Char/Misc driver patches for 4.20-rc1
Here is the big set of char/misc patches for 4.20-rc1. Loads of things here, we have new code in all of these driver subsystems: fpga stm extcon nvmem eeprom hyper-v gsmi coresight thunderbolt vmw_balloon goldfish soundwire along with lots of fixes and minor changes to other small drivers. All of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCW9Le5A8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+yn+BQCfZ6DtCIgqo0UW3dLV8Fd0wya9kw0AoNglzJJ6 YRZiaSdRiggARpNdh3ME =97BX -----END PGP SIGNATURE----- Merge tag 'char-misc-4.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc Pull char/misc driver updates from Greg KH: "Here is the big set of char/misc patches for 4.20-rc1. Loads of things here, we have new code in all of these driver subsystems: - fpga - stm - extcon - nvmem - eeprom - hyper-v - gsmi - coresight - thunderbolt - vmw_balloon - goldfish - soundwire along with lots of fixes and minor changes to other small drivers. All of these have been in linux-next for a while with no reported issues" * tag 'char-misc-4.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (245 commits) Documentation/security-bugs: Clarify treatment of embargoed information lib: Fix ia64 bootloader linkage MAINTAINERS: Clarify UIO vs UIOVEC maintainer docs/uio: fix a grammar nitpick docs: fpga: document programming fpgas using regions fpga: add devm_fpga_region_create fpga: bridge: add devm_fpga_bridge_create fpga: mgr: add devm_fpga_mgr_create hv_balloon: Replace spin_is_locked() with lockdep sgi-xp: Replace spin_is_locked() with lockdep eeprom: New ee1004 driver for DDR4 memory eeprom: at25: remove unneeded 'at25_remove' w1: IAD Register is yet readable trough iad sys file. Fix snprintf (%u for unsigned, count for max size). misc: mic: scif: remove set but not used variables 'src_dma_addr, dst_dma_addr' misc: mic: fix a DMA pool free failure platform: goldfish: pipe: Add a blank line to separate varibles and code platform: goldfish: pipe: Remove redundant casting platform: goldfish: pipe: Call misc_deregister if init fails platform: goldfish: pipe: Move the file-scope goldfish_pipe_dev variable into the driver state platform: goldfish: pipe: Move the file-scope goldfish_pipe_miscdev variable into the driver state ...
This commit is contained in:
commit
18d0eae30e
|
@ -0,0 +1,41 @@
|
|||
What: /config/stp-policy/<device>:p_sys-t.<policy>/<node>/uuid
|
||||
Date: June 2018
|
||||
KernelVersion: 4.19
|
||||
Description:
|
||||
UUID source identifier string, RW.
|
||||
Default value is randomly generated at the mkdir <node> time.
|
||||
Data coming from trace sources that use this <node> will be
|
||||
tagged with this UUID in the MIPI SyS-T packet stream, to
|
||||
allow the decoder to discern between different sources
|
||||
within the same master/channel range, and identify the
|
||||
higher level decoders that may be needed for each source.
|
||||
|
||||
What: /config/stp-policy/<device>:p_sys-t.<policy>/<node>/do_len
|
||||
Date: June 2018
|
||||
KernelVersion: 4.19
|
||||
Description:
|
||||
Include payload length in the MIPI SyS-T header, boolean.
|
||||
If enabled, the SyS-T protocol encoder will include payload
|
||||
length in each packet's metadata. This is normally redundant
|
||||
if the underlying transport protocol supports marking message
|
||||
boundaries (which STP does), so this is off by default.
|
||||
|
||||
What: /config/stp-policy/<device>:p_sys-t.<policy>/<node>/ts_interval
|
||||
Date: June 2018
|
||||
KernelVersion: 4.19
|
||||
Description:
|
||||
Time interval in milliseconds. Include a timestamp in the
|
||||
MIPI SyS-T packet metadata, if this many milliseconds have
|
||||
passed since the previous packet from this source. Zero is
|
||||
the default and stands for "never send the timestamp".
|
||||
|
||||
What: /config/stp-policy/<device>:p_sys-t.<policy>/<node>/clocksync_interval
|
||||
Date: June 2018
|
||||
KernelVersion: 4.19
|
||||
Description:
|
||||
Time interval in milliseconds. Send a CLOCKSYNC packet if
|
||||
this many milliseconds have passed since the previous
|
||||
CLOCKSYNC packet from this source. Zero is the default and
|
||||
stands for "never send the CLOCKSYNC". It makes sense to
|
||||
use this option with sources that generate constant and/or
|
||||
periodic data, like stm_heartbeat.
|
|
@ -0,0 +1,21 @@
|
|||
What: /sys/bus/vmbus/devices/.../driver_override
|
||||
Date: August 2019
|
||||
Contact: Stephen Hemminger <sthemmin@microsoft.com>
|
||||
Description:
|
||||
This file allows the driver for a device to be specified which
|
||||
will override standard static and dynamic ID matching. When
|
||||
specified, only a driver with a name matching the value written
|
||||
to driver_override will have an opportunity to bind to the
|
||||
device. The override is specified by writing a string to the
|
||||
driver_override file (echo uio_hv_generic > driver_override) and
|
||||
may be cleared with an empty string (echo > driver_override).
|
||||
This returns the device to standard matching rules binding.
|
||||
Writing to driver_override does not automatically unbind the
|
||||
device from its current driver or make any attempt to
|
||||
automatically load the specified driver. If no driver with a
|
||||
matching name is currently loaded in the kernel, the device
|
||||
will not bind to any driver. This also allows devices to
|
||||
opt-out of driver binding using a driver_override name such as
|
||||
"none". Only a single driver may be specified in the override,
|
||||
there is no support for parsing delimiters.
|
||||
|
|
@ -26,23 +26,34 @@ information is helpful. Any exploit code is very helpful and will not
|
|||
be released without consent from the reporter unless it has already been
|
||||
made public.
|
||||
|
||||
Disclosure
|
||||
----------
|
||||
Disclosure and embargoed information
|
||||
------------------------------------
|
||||
|
||||
The goal of the Linux kernel security team is to work with the bug
|
||||
submitter to understand and fix the bug. We prefer to publish the fix as
|
||||
soon as possible, but try to avoid public discussion of the bug itself
|
||||
and leave that to others.
|
||||
The security list is not a disclosure channel. For that, see Coordination
|
||||
below.
|
||||
|
||||
Publishing the fix may be delayed when the bug or the fix is not yet
|
||||
fully understood, the solution is not well-tested or for vendor
|
||||
coordination. However, we expect these delays to be short, measurable in
|
||||
days, not weeks or months. A release date is negotiated by the security
|
||||
team working with the bug submitter as well as vendors. However, the
|
||||
kernel security team holds the final say when setting a timeframe. The
|
||||
timeframe varies from immediate (esp. if it's already publicly known bug)
|
||||
to a few weeks. As a basic default policy, we expect report date to
|
||||
release date to be on the order of 7 days.
|
||||
Once a robust fix has been developed, our preference is to release the
|
||||
fix in a timely fashion, treating it no differently than any of the other
|
||||
thousands of changes and fixes the Linux kernel project releases every
|
||||
month.
|
||||
|
||||
However, at the request of the reporter, we will postpone releasing the
|
||||
fix for up to 5 business days after the date of the report or after the
|
||||
embargo has lifted; whichever comes first. The only exception to that
|
||||
rule is if the bug is publicly known, in which case the preference is to
|
||||
release the fix as soon as it's available.
|
||||
|
||||
Whilst embargoed information may be shared with trusted individuals in
|
||||
order to develop a fix, such information will not be published alongside
|
||||
the fix or on any other disclosure channel without the permission of the
|
||||
reporter. This includes but is not limited to the original bug report
|
||||
and followup discussions (if any), exploits, CVE information or the
|
||||
identity of the reporter.
|
||||
|
||||
In other words our only interest is in getting bugs fixed. All other
|
||||
information submitted to the security list and any followup discussions
|
||||
of the report are treated confidentially even after the embargo has been
|
||||
lifted, in perpetuity.
|
||||
|
||||
Coordination
|
||||
------------
|
||||
|
@ -68,7 +79,7 @@ may delay the bug handling. If a reporter wishes to have a CVE identifier
|
|||
assigned ahead of public disclosure, they will need to contact the private
|
||||
linux-distros list, described above. When such a CVE identifier is known
|
||||
before a patch is provided, it is desirable to mention it in the commit
|
||||
message, though.
|
||||
message if the reporter agrees.
|
||||
|
||||
Non-disclosure agreements
|
||||
-------------------------
|
||||
|
|
|
@ -54,9 +54,7 @@ its hardware characteristcs.
|
|||
clocks the core of that coresight component. The latter clock
|
||||
is optional.
|
||||
|
||||
* port or ports: The representation of the component's port
|
||||
layout using the generic DT graph presentation found in
|
||||
"bindings/graph.txt".
|
||||
* port or ports: see "Graph bindings for Coresight" below.
|
||||
|
||||
* Additional required properties for System Trace Macrocells (STM):
|
||||
* reg: along with the physical base address and length of the register
|
||||
|
@ -73,7 +71,7 @@ its hardware characteristcs.
|
|||
AMBA markee):
|
||||
- "arm,coresight-replicator"
|
||||
|
||||
* port or ports: same as above.
|
||||
* port or ports: see "Graph bindings for Coresight" below.
|
||||
|
||||
* Optional properties for ETM/PTMs:
|
||||
|
||||
|
@ -96,6 +94,20 @@ its hardware characteristcs.
|
|||
* interrupts : Exactly one SPI may be listed for reporting the address
|
||||
error
|
||||
|
||||
Graph bindings for Coresight
|
||||
-------------------------------
|
||||
|
||||
Coresight components are interconnected to create a data path for the flow of
|
||||
trace data generated from the "sources" to their collection points "sink".
|
||||
Each coresight component must describe the "input" and "output" connections.
|
||||
The connections must be described via generic DT graph bindings as described
|
||||
by the "bindings/graph.txt", where each "port" along with an "endpoint"
|
||||
component represents a hardware port and the connection.
|
||||
|
||||
* All output ports must be listed inside a child node named "out-ports"
|
||||
* All input ports must be listed inside a child node named "in-ports".
|
||||
* Port address must match the hardware port number.
|
||||
|
||||
Example:
|
||||
|
||||
1. Sinks
|
||||
|
@ -105,10 +117,11 @@ Example:
|
|||
|
||||
clocks = <&oscclk6a>;
|
||||
clock-names = "apb_pclk";
|
||||
port {
|
||||
etb_in_port: endpoint@0 {
|
||||
slave-mode;
|
||||
remote-endpoint = <&replicator_out_port0>;
|
||||
in-ports {
|
||||
port {
|
||||
etb_in_port: endpoint@0 {
|
||||
remote-endpoint = <&replicator_out_port0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -119,10 +132,11 @@ Example:
|
|||
|
||||
clocks = <&oscclk6a>;
|
||||
clock-names = "apb_pclk";
|
||||
port {
|
||||
tpiu_in_port: endpoint@0 {
|
||||
slave-mode;
|
||||
remote-endpoint = <&replicator_out_port1>;
|
||||
in-ports {
|
||||
port {
|
||||
tpiu_in_port: endpoint@0 {
|
||||
remote-endpoint = <&replicator_out_port1>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -133,22 +147,16 @@ Example:
|
|||
|
||||
clocks = <&oscclk6a>;
|
||||
clock-names = "apb_pclk";
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* input port */
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
in-ports {
|
||||
port {
|
||||
etr_in_port: endpoint {
|
||||
slave-mode;
|
||||
remote-endpoint = <&replicator2_out_port0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* CATU link represented by output port */
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
out-ports {
|
||||
port {
|
||||
etr_out_port: endpoint {
|
||||
remote-endpoint = <&catu_in_port>;
|
||||
};
|
||||
|
@ -163,7 +171,7 @@ Example:
|
|||
*/
|
||||
compatible = "arm,coresight-replicator";
|
||||
|
||||
ports {
|
||||
out-ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
|
@ -181,12 +189,11 @@ Example:
|
|||
remote-endpoint = <&tpiu_in_port>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* replicator input port */
|
||||
port@2 {
|
||||
reg = <0>;
|
||||
in-ports {
|
||||
port {
|
||||
replicator_in_port0: endpoint {
|
||||
slave-mode;
|
||||
remote-endpoint = <&funnel_out_port0>;
|
||||
};
|
||||
};
|
||||
|
@ -199,40 +206,36 @@ Example:
|
|||
|
||||
clocks = <&oscclk6a>;
|
||||
clock-names = "apb_pclk";
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* funnel output port */
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
out-ports {
|
||||
port {
|
||||
funnel_out_port0: endpoint {
|
||||
remote-endpoint =
|
||||
<&replicator_in_port0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* funnel input ports */
|
||||
port@1 {
|
||||
in-ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
funnel_in_port0: endpoint {
|
||||
slave-mode;
|
||||
remote-endpoint = <&ptm0_out_port>;
|
||||
};
|
||||
};
|
||||
|
||||
port@2 {
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
funnel_in_port1: endpoint {
|
||||
slave-mode;
|
||||
remote-endpoint = <&ptm1_out_port>;
|
||||
};
|
||||
};
|
||||
|
||||
port@3 {
|
||||
port@2 {
|
||||
reg = <2>;
|
||||
funnel_in_port2: endpoint {
|
||||
slave-mode;
|
||||
remote-endpoint = <&etm0_out_port>;
|
||||
};
|
||||
};
|
||||
|
@ -248,9 +251,11 @@ Example:
|
|||
cpu = <&cpu0>;
|
||||
clocks = <&oscclk6a>;
|
||||
clock-names = "apb_pclk";
|
||||
port {
|
||||
ptm0_out_port: endpoint {
|
||||
remote-endpoint = <&funnel_in_port0>;
|
||||
out-ports {
|
||||
port {
|
||||
ptm0_out_port: endpoint {
|
||||
remote-endpoint = <&funnel_in_port0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -262,9 +267,11 @@ Example:
|
|||
cpu = <&cpu1>;
|
||||
clocks = <&oscclk6a>;
|
||||
clock-names = "apb_pclk";
|
||||
port {
|
||||
ptm1_out_port: endpoint {
|
||||
remote-endpoint = <&funnel_in_port1>;
|
||||
out-ports {
|
||||
port {
|
||||
ptm1_out_port: endpoint {
|
||||
remote-endpoint = <&funnel_in_port1>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -278,9 +285,11 @@ Example:
|
|||
|
||||
clocks = <&soc_smc50mhz>;
|
||||
clock-names = "apb_pclk";
|
||||
port {
|
||||
stm_out_port: endpoint {
|
||||
remote-endpoint = <&main_funnel_in_port2>;
|
||||
out-ports {
|
||||
port {
|
||||
stm_out_port: endpoint {
|
||||
remote-endpoint = <&main_funnel_in_port2>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -295,10 +304,11 @@ Example:
|
|||
clock-names = "apb_pclk";
|
||||
|
||||
interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
|
||||
port {
|
||||
catu_in_port: endpoint {
|
||||
slave-mode;
|
||||
remote-endpoint = <&etr_out_port>;
|
||||
in-ports {
|
||||
port {
|
||||
catu_in_port: endpoint {
|
||||
remote-endpoint = <&etr_out_port>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,12 @@ FPGA Bridge
|
|||
API to implement a new FPGA bridge
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* struct :c:type:`fpga_bridge` — The FPGA Bridge structure
|
||||
* struct :c:type:`fpga_bridge_ops` — Low level Bridge driver ops
|
||||
* :c:func:`devm_fpga_bridge_create()` — Allocate and init a bridge struct
|
||||
* :c:func:`fpga_bridge_register()` — Register a bridge
|
||||
* :c:func:`fpga_bridge_unregister()` — Unregister a bridge
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-bridge.h
|
||||
:functions: fpga_bridge
|
||||
|
||||
|
@ -11,39 +17,10 @@ API to implement a new FPGA bridge
|
|||
:functions: fpga_bridge_ops
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_create
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_free
|
||||
:functions: devm_fpga_bridge_create
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_register
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_unregister
|
||||
|
||||
API to control an FPGA bridge
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You probably won't need these directly. FPGA regions should handle this.
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: of_fpga_bridge_get
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_get
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_put
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_get_to_list
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: of_fpga_bridge_get_to_list
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_enable
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_disable
|
||||
|
|
|
@ -49,18 +49,14 @@ probe function calls fpga_mgr_register(), such as::
|
|||
* them in priv
|
||||
*/
|
||||
|
||||
mgr = fpga_mgr_create(dev, "Altera SOCFPGA FPGA Manager",
|
||||
&socfpga_fpga_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, "Altera SOCFPGA FPGA Manager",
|
||||
&socfpga_fpga_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int socfpga_fpga_remove(struct platform_device *pdev)
|
||||
|
@ -102,67 +98,19 @@ The ops include a .state function which will determine the state the FPGA is in
|
|||
and return a code of type enum fpga_mgr_states. It doesn't result in a change
|
||||
in state.
|
||||
|
||||
How to write an image buffer to a supported FPGA
|
||||
------------------------------------------------
|
||||
|
||||
Some sample code::
|
||||
|
||||
#include <linux/fpga/fpga-mgr.h>
|
||||
|
||||
struct fpga_manager *mgr;
|
||||
struct fpga_image_info *info;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Get a reference to FPGA manager. The manager is not locked, so you can
|
||||
* hold onto this reference without it preventing programming.
|
||||
*
|
||||
* This example uses the device node of the manager. Alternatively, use
|
||||
* fpga_mgr_get(dev) instead if you have the device.
|
||||
*/
|
||||
mgr = of_fpga_mgr_get(mgr_node);
|
||||
|
||||
/* struct with information about the FPGA image to program. */
|
||||
info = fpga_image_info_alloc(dev);
|
||||
|
||||
/* flags indicates whether to do full or partial reconfiguration */
|
||||
info->flags = FPGA_MGR_PARTIAL_RECONFIG;
|
||||
|
||||
/*
|
||||
* At this point, indicate where the image is. This is pseudo-code; you're
|
||||
* going to use one of these three.
|
||||
*/
|
||||
if (image is in a scatter gather table) {
|
||||
|
||||
info->sgt = [your scatter gather table]
|
||||
|
||||
} else if (image is in a buffer) {
|
||||
|
||||
info->buf = [your image buffer]
|
||||
info->count = [image buffer size]
|
||||
|
||||
} else if (image is in a firmware file) {
|
||||
|
||||
info->firmware_name = devm_kstrdup(dev, firmware_name, GFP_KERNEL);
|
||||
|
||||
}
|
||||
|
||||
/* Get exclusive control of FPGA manager */
|
||||
ret = fpga_mgr_lock(mgr);
|
||||
|
||||
/* Load the buffer to the FPGA */
|
||||
ret = fpga_mgr_buf_load(mgr, &info, buf, count);
|
||||
|
||||
/* Release the FPGA manager */
|
||||
fpga_mgr_unlock(mgr);
|
||||
fpga_mgr_put(mgr);
|
||||
|
||||
/* Deallocate the image info if you're done with it */
|
||||
fpga_image_info_free(info);
|
||||
|
||||
API for implementing a new FPGA Manager driver
|
||||
----------------------------------------------
|
||||
|
||||
* ``fpga_mgr_states`` — Values for :c:member:`fpga_manager->state`.
|
||||
* struct :c:type:`fpga_manager` — the FPGA manager struct
|
||||
* struct :c:type:`fpga_manager_ops` — Low level FPGA manager driver ops
|
||||
* :c:func:`devm_fpga_mgr_create` — Allocate and init a manager struct
|
||||
* :c:func:`fpga_mgr_register` — Register an FPGA manager
|
||||
* :c:func:`fpga_mgr_unregister` — Unregister an FPGA manager
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:functions: fpga_mgr_states
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:functions: fpga_manager
|
||||
|
||||
|
@ -170,56 +118,10 @@ API for implementing a new FPGA Manager driver
|
|||
:functions: fpga_manager_ops
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_create
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_free
|
||||
:functions: devm_fpga_mgr_create
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_register
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_unregister
|
||||
|
||||
API for programming an FPGA
|
||||
---------------------------
|
||||
|
||||
FPGA Manager flags
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:doc: FPGA Manager flags
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:functions: fpga_image_info
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:functions: fpga_mgr_states
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_image_info_alloc
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_image_info_free
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: of_fpga_mgr_get
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_get
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_put
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_lock
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_unlock
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:functions: fpga_mgr_states
|
||||
|
||||
Note - use :c:func:`fpga_region_program_fpga()` instead of :c:func:`fpga_mgr_load()`
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_load
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
In-kernel API for FPGA Programming
|
||||
==================================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The in-kernel API for FPGA programming is a combination of APIs from
|
||||
FPGA manager, bridge, and regions. The actual function used to
|
||||
trigger FPGA programming is :c:func:`fpga_region_program_fpga()`.
|
||||
|
||||
:c:func:`fpga_region_program_fpga()` uses functionality supplied by
|
||||
the FPGA manager and bridges. It will:
|
||||
|
||||
* lock the region's mutex
|
||||
* lock the mutex of the region's FPGA manager
|
||||
* build a list of FPGA bridges if a method has been specified to do so
|
||||
* disable the bridges
|
||||
* program the FPGA using info passed in :c:member:`fpga_region->info`.
|
||||
* re-enable the bridges
|
||||
* release the locks
|
||||
|
||||
The struct fpga_image_info specifies what FPGA image to program. It is
|
||||
allocated/freed by :c:func:`fpga_image_info_alloc()` and freed with
|
||||
:c:func:`fpga_image_info_free()`
|
||||
|
||||
How to program an FPGA using a region
|
||||
-------------------------------------
|
||||
|
||||
When the FPGA region driver probed, it was given a pointer to an FPGA manager
|
||||
driver so it knows which manager to use. The region also either has a list of
|
||||
bridges to control during programming or it has a pointer to a function that
|
||||
will generate that list. Here's some sample code of what to do next::
|
||||
|
||||
#include <linux/fpga/fpga-mgr.h>
|
||||
#include <linux/fpga/fpga-region.h>
|
||||
|
||||
struct fpga_image_info *info;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* First, alloc the struct with information about the FPGA image to
|
||||
* program.
|
||||
*/
|
||||
info = fpga_image_info_alloc(dev);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Set flags as needed, such as: */
|
||||
info->flags = FPGA_MGR_PARTIAL_RECONFIG;
|
||||
|
||||
/*
|
||||
* Indicate where the FPGA image is. This is pseudo-code; you're
|
||||
* going to use one of these three.
|
||||
*/
|
||||
if (image is in a scatter gather table) {
|
||||
|
||||
info->sgt = [your scatter gather table]
|
||||
|
||||
} else if (image is in a buffer) {
|
||||
|
||||
info->buf = [your image buffer]
|
||||
info->count = [image buffer size]
|
||||
|
||||
} else if (image is in a firmware file) {
|
||||
|
||||
info->firmware_name = devm_kstrdup(dev, firmware_name,
|
||||
GFP_KERNEL);
|
||||
|
||||
}
|
||||
|
||||
/* Add info to region and do the programming */
|
||||
region->info = info;
|
||||
ret = fpga_region_program_fpga(region);
|
||||
|
||||
/* Deallocate the image info if you're done with it */
|
||||
region->info = NULL;
|
||||
fpga_image_info_free(info);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Now enumerate whatever hardware has appeared in the FPGA. */
|
||||
|
||||
API for programming an FPGA
|
||||
---------------------------
|
||||
|
||||
* :c:func:`fpga_region_program_fpga` — Program an FPGA
|
||||
* :c:type:`fpga_image_info` — Specifies what FPGA image to program
|
||||
* :c:func:`fpga_image_info_alloc()` — Allocate an FPGA image info struct
|
||||
* :c:func:`fpga_image_info_free()` — Free an FPGA image info struct
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-region.c
|
||||
:functions: fpga_region_program_fpga
|
||||
|
||||
FPGA Manager flags
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:doc: FPGA Manager flags
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-mgr.h
|
||||
:functions: fpga_image_info
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_image_info_alloc
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_image_info_free
|
|
@ -34,41 +34,6 @@ fpga_image_info including:
|
|||
* flags indicating specifics such as whether the image is for partial
|
||||
reconfiguration.
|
||||
|
||||
How to program an FPGA using a region
|
||||
-------------------------------------
|
||||
|
||||
First, allocate the info struct::
|
||||
|
||||
info = fpga_image_info_alloc(dev);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
Set flags as needed, i.e.::
|
||||
|
||||
info->flags |= FPGA_MGR_PARTIAL_RECONFIG;
|
||||
|
||||
Point to your FPGA image, such as::
|
||||
|
||||
info->sgt = &sgt;
|
||||
|
||||
Add info to region and do the programming::
|
||||
|
||||
region->info = info;
|
||||
ret = fpga_region_program_fpga(region);
|
||||
|
||||
:c:func:`fpga_region_program_fpga()` operates on info passed in the
|
||||
fpga_image_info (region->info). This function will attempt to:
|
||||
|
||||
* lock the region's mutex
|
||||
* lock the region's FPGA manager
|
||||
* build a list of FPGA bridges if a method has been specified to do so
|
||||
* disable the bridges
|
||||
* program the FPGA
|
||||
* re-enable the bridges
|
||||
* release the locks
|
||||
|
||||
Then you will want to enumerate whatever hardware has appeared in the FPGA.
|
||||
|
||||
How to add a new FPGA region
|
||||
----------------------------
|
||||
|
||||
|
@ -77,26 +42,62 @@ An example of usage can be seen in the probe function of [#f2]_.
|
|||
.. [#f1] ../devicetree/bindings/fpga/fpga-region.txt
|
||||
.. [#f2] ../../drivers/fpga/of-fpga-region.c
|
||||
|
||||
API to program an FPGA
|
||||
----------------------
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-region.c
|
||||
:functions: fpga_region_program_fpga
|
||||
|
||||
API to add a new FPGA region
|
||||
----------------------------
|
||||
|
||||
* struct :c:type:`fpga_region` — The FPGA region struct
|
||||
* :c:func:`devm_fpga_region_create` — Allocate and init a region struct
|
||||
* :c:func:`fpga_region_register` — Register an FPGA region
|
||||
* :c:func:`fpga_region_unregister` — Unregister an FPGA region
|
||||
|
||||
The FPGA region's probe function will need to get a reference to the FPGA
|
||||
Manager it will be using to do the programming. This usually would happen
|
||||
during the region's probe function.
|
||||
|
||||
* :c:func:`fpga_mgr_get` — Get a reference to an FPGA manager, raise ref count
|
||||
* :c:func:`of_fpga_mgr_get` — Get a reference to an FPGA manager, raise ref count,
|
||||
given a device node.
|
||||
* :c:func:`fpga_mgr_put` — Put an FPGA manager
|
||||
|
||||
The FPGA region will need to specify which bridges to control while programming
|
||||
the FPGA. The region driver can build a list of bridges during probe time
|
||||
(:c:member:`fpga_region->bridge_list`) or it can have a function that creates
|
||||
the list of bridges to program just before programming
|
||||
(:c:member:`fpga_region->get_bridges`). The FPGA bridge framework supplies the
|
||||
following APIs to handle building or tearing down that list.
|
||||
|
||||
* :c:func:`fpga_bridge_get_to_list` — Get a ref of an FPGA bridge, add it to a
|
||||
list
|
||||
* :c:func:`of_fpga_bridge_get_to_list` — Get a ref of an FPGA bridge, add it to a
|
||||
list, given a device node
|
||||
* :c:func:`fpga_bridges_put` — Given a list of bridges, put them
|
||||
|
||||
.. kernel-doc:: include/linux/fpga/fpga-region.h
|
||||
:functions: fpga_region
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-region.c
|
||||
:functions: fpga_region_create
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-region.c
|
||||
:functions: fpga_region_free
|
||||
:functions: devm_fpga_region_create
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-region.c
|
||||
:functions: fpga_region_register
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-region.c
|
||||
:functions: fpga_region_unregister
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_get
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: of_fpga_mgr_get
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-mgr.c
|
||||
:functions: fpga_mgr_put
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridge_get_to_list
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: of_fpga_bridge_get_to_list
|
||||
|
||||
.. kernel-doc:: drivers/fpga/fpga-bridge.c
|
||||
:functions: fpga_bridges_put
|
||||
|
|
|
@ -11,3 +11,5 @@ FPGA Subsystem
|
|||
fpga-mgr
|
||||
fpga-bridge
|
||||
fpga-region
|
||||
fpga-programming
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ FPGA Region
|
|||
-----------
|
||||
|
||||
If you are adding a new interface to the FPGA framework, add it on top
|
||||
of an FPGA region to allow the most reuse of your interface.
|
||||
of an FPGA region.
|
||||
|
||||
The FPGA Region framework (fpga-region.c) associates managers and
|
||||
bridges as reconfigurable regions. A region may refer to the whole
|
||||
|
|
|
@ -101,6 +101,34 @@ interface. ::
|
|||
+--------------------+ | |
|
||||
+----------------+
|
||||
|
||||
Example 5: Stereo Stream with L and R channel is rendered by 2 Masters, each
|
||||
rendering one channel, and is received by two different Slaves, each
|
||||
receiving one channel. Both Masters and both Slaves are using single port. ::
|
||||
|
||||
+---------------+ Clock Signal +---------------+
|
||||
| Master +----------------------------------+ Slave |
|
||||
| Interface | | Interface |
|
||||
| 1 | | 1 |
|
||||
| | Data Signal | |
|
||||
| L +----------------------------------+ L |
|
||||
| (Data) | Data Direction | (Data) |
|
||||
+---------------+ +-----------------------> +---------------+
|
||||
|
||||
+---------------+ Clock Signal +---------------+
|
||||
| Master +----------------------------------+ Slave |
|
||||
| Interface | | Interface |
|
||||
| 2 | | 2 |
|
||||
| | Data Signal | |
|
||||
| R +----------------------------------+ R |
|
||||
| (Data) | Data Direction | (Data) |
|
||||
+---------------+ +-----------------------> +---------------+
|
||||
|
||||
Note: In multi-link cases like above, to lock, one would acquire a global
|
||||
lock and then go on locking bus instances. But, in this case the caller
|
||||
framework(ASoC DPCM) guarantees that stream operations on a card are
|
||||
always serialized. So, there is no race condition and hence no need for
|
||||
global lock.
|
||||
|
||||
SoundWire Stream Management flow
|
||||
================================
|
||||
|
||||
|
@ -174,6 +202,7 @@ per stream. From ASoC DPCM framework, this stream state maybe linked to
|
|||
.startup() operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_alloc_stream(char * stream_name);
|
||||
|
||||
|
||||
|
@ -200,6 +229,7 @@ only be invoked once by respective Master(s) and Slave(s). From ASoC DPCM
|
|||
framework, this stream state is linked to .hw_params() operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_stream_add_master(struct sdw_bus * bus,
|
||||
struct sdw_stream_config * stream_config,
|
||||
struct sdw_ports_config * ports_config,
|
||||
|
@ -245,6 +275,7 @@ stream. From ASoC DPCM framework, this stream state is linked to
|
|||
.prepare() operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_prepare_stream(struct sdw_stream_runtime * stream);
|
||||
|
||||
|
||||
|
@ -274,6 +305,7 @@ stream. From ASoC DPCM framework, this stream state is linked to
|
|||
.trigger() start operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_enable_stream(struct sdw_stream_runtime * stream);
|
||||
|
||||
SDW_STREAM_DISABLED
|
||||
|
@ -301,6 +333,7 @@ per stream. From ASoC DPCM framework, this stream state is linked to
|
|||
.trigger() stop operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_disable_stream(struct sdw_stream_runtime * stream);
|
||||
|
||||
|
||||
|
@ -325,6 +358,7 @@ per stream. From ASoC DPCM framework, this stream state is linked to
|
|||
.trigger() stop operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_deprepare_stream(struct sdw_stream_runtime * stream);
|
||||
|
||||
|
||||
|
@ -349,6 +383,7 @@ all the Master(s) and Slave(s) associated with stream. From ASoC DPCM
|
|||
framework, this stream state is linked to .hw_free() operation.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_stream_remove_master(struct sdw_bus * bus,
|
||||
struct sdw_stream_runtime * stream);
|
||||
int sdw_stream_remove_slave(struct sdw_slave * slave,
|
||||
|
@ -361,6 +396,7 @@ stream assigned as part of ALLOCATED state.
|
|||
In .shutdown() the data structure maintaining stream state are freed up.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void sdw_release_stream(struct sdw_stream_runtime * stream);
|
||||
|
||||
Not Supported
|
||||
|
|
|
@ -463,8 +463,8 @@ Getting information about your UIO device
|
|||
|
||||
Information about all UIO devices is available in sysfs. The first thing
|
||||
you should do in your driver is check ``name`` and ``version`` to make
|
||||
sure your talking to the right device and that its kernel driver has the
|
||||
version you expect.
|
||||
sure you're talking to the right device and that its kernel driver has
|
||||
the version you expect.
|
||||
|
||||
You should also make sure that the memory mapping you need exists and
|
||||
has the size you expect.
|
||||
|
|
|
@ -58,6 +58,37 @@ static int qfprom_probe(struct platform_device *pdev)
|
|||
It is mandatory that the NVMEM provider has a regmap associated with its
|
||||
struct device. Failure to do would return error code from nvmem_register().
|
||||
|
||||
Users of board files can define and register nvmem cells using the
|
||||
nvmem_cell_table struct:
|
||||
|
||||
static struct nvmem_cell_info foo_nvmem_cells[] = {
|
||||
{
|
||||
.name = "macaddr",
|
||||
.offset = 0x7f00,
|
||||
.bytes = ETH_ALEN,
|
||||
}
|
||||
};
|
||||
|
||||
static struct nvmem_cell_table foo_nvmem_cell_table = {
|
||||
.nvmem_name = "i2c-eeprom",
|
||||
.cells = foo_nvmem_cells,
|
||||
.ncells = ARRAY_SIZE(foo_nvmem_cells),
|
||||
};
|
||||
|
||||
nvmem_add_cell_table(&foo_nvmem_cell_table);
|
||||
|
||||
Additionally it is possible to create nvmem cell lookup entries and register
|
||||
them with the nvmem framework from machine code as shown in the example below:
|
||||
|
||||
static struct nvmem_cell_lookup foo_nvmem_lookup = {
|
||||
.nvmem_name = "i2c-eeprom",
|
||||
.cell_name = "macaddr",
|
||||
.dev_id = "foo_mac.0",
|
||||
.con_id = "mac-address",
|
||||
};
|
||||
|
||||
nvmem_add_cell_lookups(&foo_nvmem_lookup, 1);
|
||||
|
||||
NVMEM Consumers
|
||||
+++++++++++++++
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===================
|
||||
System Trace Module
|
||||
===================
|
||||
|
@ -53,12 +55,30 @@ under "user" directory from the example above and this new rule will
|
|||
be used for trace sources with the id string of "user/dummy".
|
||||
|
||||
Trace sources have to open the stm class device's node and write their
|
||||
trace data into its file descriptor. In order to identify themselves
|
||||
to the policy, they need to do a STP_POLICY_ID_SET ioctl on this file
|
||||
descriptor providing their id string. Otherwise, they will be
|
||||
automatically allocated a master/channel pair upon first write to this
|
||||
file descriptor according to the "default" rule of the policy, if such
|
||||
exists.
|
||||
trace data into its file descriptor.
|
||||
|
||||
In order to find an appropriate policy node for a given trace source,
|
||||
several mechanisms can be used. First, a trace source can explicitly
|
||||
identify itself by calling an STP_POLICY_ID_SET ioctl on the character
|
||||
device's file descriptor, providing their id string, before they write
|
||||
any data there. Secondly, if they chose not to perform the explicit
|
||||
identification (because you may not want to patch existing software
|
||||
to do this), they can just start writing the data, at which point the
|
||||
stm core will try to find a policy node with the name matching the
|
||||
task's name (e.g., "syslogd") and if one exists, it will be used.
|
||||
Thirdly, if the task name can't be found among the policy nodes, the
|
||||
catch-all entry "default" will be used, if it exists. This entry also
|
||||
needs to be created and configured by the system administrator or
|
||||
whatever tools are taking care of the policy configuration. Finally,
|
||||
if all the above steps failed, the write() to an stm file descriptor
|
||||
will return a error (EINVAL).
|
||||
|
||||
Previously, if no policy nodes were found for a trace source, the stm
|
||||
class would silently fall back to allocating the first available
|
||||
contiguous range of master/channels from the beginning of the device's
|
||||
master/channel range. The new requirement for a policy node to exist
|
||||
will help programmers and sysadmins identify gaps in configuration
|
||||
and have better control over the un-identified sources.
|
||||
|
||||
Some STM devices may allow direct mapping of the channel mmio regions
|
||||
to userspace for zero-copy writing. One mappable page (in terms of
|
||||
|
@ -92,9 +112,9 @@ allocated for the device according to the policy configuration. If
|
|||
there's a node in the root of the policy directory that matches the
|
||||
stm_source device's name (for example, "console"), this node will be
|
||||
used to allocate master and channel numbers. If there's no such policy
|
||||
node, the stm core will pick the first contiguous chunk of channels
|
||||
within the first available master. Note that the node must exist
|
||||
before the stm_source device is connected to its stm device.
|
||||
node, the stm core will use the catch-all entry "default", if one
|
||||
exists. If neither policy nodes exist, the write() to stm_source_link
|
||||
will return an error.
|
||||
|
||||
stm_console
|
||||
===========
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===================
|
||||
MIPI SyS-T over STP
|
||||
===================
|
||||
|
||||
The MIPI SyS-T protocol driver can be used with STM class devices to
|
||||
generate standardized trace stream. Aside from being a standard, it
|
||||
provides better trace source identification and timestamp correlation.
|
||||
|
||||
In order to use the MIPI SyS-T protocol driver with your STM device,
|
||||
first, you'll need CONFIG_STM_PROTO_SYS_T.
|
||||
|
||||
Now, you can select which protocol driver you want to use when you create
|
||||
a policy for your STM device, by specifying it in the policy name:
|
||||
|
||||
# mkdir /config/stp-policy/dummy_stm.0:p_sys-t.my-policy/
|
||||
|
||||
In other words, the policy name format is extended like this:
|
||||
|
||||
<device_name>:<protocol_name>.<policy_name>
|
||||
|
||||
With Intel TH, therefore it can look like "0-sth:p_sys-t.my-policy".
|
||||
|
||||
If the protocol name is omitted, the STM class will chose whichever
|
||||
protocol driver was loaded first.
|
||||
|
||||
You can also double check that everything is working as expected by
|
||||
|
||||
# cat /config/stp-policy/dummy_stm.0:p_sys-t.my-policy/protocol
|
||||
p_sys-t
|
||||
|
||||
Now, with the MIPI SyS-T protocol driver, each policy node in the
|
||||
configfs gets a few additional attributes, which determine per-source
|
||||
parameters specific to the protocol:
|
||||
|
||||
# mkdir /config/stp-policy/dummy_stm.0:p_sys-t.my-policy/default
|
||||
# ls /config/stp-policy/dummy_stm.0:p_sys-t.my-policy/default
|
||||
channels
|
||||
clocksync_interval
|
||||
do_len
|
||||
masters
|
||||
ts_interval
|
||||
uuid
|
||||
|
||||
The most important one here is the "uuid", which determines the UUID
|
||||
that will be used to tag all data coming from this source. It is
|
||||
automatically generated when a new node is created, but it is likely
|
||||
that you would want to change it.
|
||||
|
||||
do_len switches on/off the additional "payload length" field in the
|
||||
MIPI SyS-T message header. It is off by default as the STP already
|
||||
marks message boundaries.
|
||||
|
||||
ts_interval and clocksync_interval determine how much time in milliseconds
|
||||
can pass before we need to include a protocol (not transport, aka STP)
|
||||
timestamp in a message header or send a CLOCKSYNC packet, respectively.
|
||||
|
||||
See Documentation/ABI/testing/configfs-stp-policy-p_sys-t for more
|
||||
details.
|
||||
|
||||
* [1] https://www.mipi.org/specifications/sys-t
|
11
MAINTAINERS
11
MAINTAINERS
|
@ -932,6 +932,7 @@ M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|||
M: Arve Hjønnevåg <arve@android.com>
|
||||
M: Todd Kjos <tkjos@android.com>
|
||||
M: Martijn Coenen <maco@android.com>
|
||||
M: Joel Fernandes <joel@joelfernandes.org>
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
|
||||
L: devel@driverdev.osuosl.org
|
||||
S: Supported
|
||||
|
@ -13757,7 +13758,7 @@ F: sound/soc/
|
|||
F: include/sound/soc*
|
||||
|
||||
SOUNDWIRE SUBSYSTEM
|
||||
M: Vinod Koul <vinod.koul@intel.com>
|
||||
M: Vinod Koul <vkoul@kernel.org>
|
||||
M: Sanyog Kale <sanyog.r.kale@intel.com>
|
||||
R: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
|
@ -15523,13 +15524,19 @@ F: arch/x86/um/
|
|||
F: fs/hostfs/
|
||||
F: fs/hppfs/
|
||||
|
||||
USERSPACE COPYIN/COPYOUT (UIOVEC)
|
||||
M: Alexander Viro <viro@zeniv.linux.org.uk>
|
||||
S: Maintained
|
||||
F: lib/iov_iter.c
|
||||
F: include/linux/uio.h
|
||||
|
||||
USERSPACE I/O (UIO)
|
||||
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
|
||||
F: Documentation/driver-api/uio-howto.rst
|
||||
F: drivers/uio/
|
||||
F: include/linux/uio*.h
|
||||
F: include/linux/uio_driver.h
|
||||
|
||||
UTIL-LINUX PACKAGE
|
||||
M: Karel Zak <kzak@redhat.com>
|
||||
|
|
|
@ -10,7 +10,7 @@ if ANDROID
|
|||
|
||||
config ANDROID_BINDER_IPC
|
||||
bool "Android Binder IPC Driver"
|
||||
depends on MMU
|
||||
depends on MMU && !CPU_CACHE_VIVT
|
||||
default n
|
||||
---help---
|
||||
Binder is used in Android for both communication between processes,
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
#include <linux/security.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/ratelimit.h>
|
||||
#include <linux/syscalls.h>
|
||||
|
||||
#include <uapi/linux/android/binder.h>
|
||||
|
||||
|
@ -457,9 +458,8 @@ struct binder_ref {
|
|||
};
|
||||
|
||||
enum binder_deferred_state {
|
||||
BINDER_DEFERRED_PUT_FILES = 0x01,
|
||||
BINDER_DEFERRED_FLUSH = 0x02,
|
||||
BINDER_DEFERRED_RELEASE = 0x04,
|
||||
BINDER_DEFERRED_FLUSH = 0x01,
|
||||
BINDER_DEFERRED_RELEASE = 0x02,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -480,9 +480,6 @@ enum binder_deferred_state {
|
|||
* (invariant after initialized)
|
||||
* @tsk task_struct for group_leader of process
|
||||
* (invariant after initialized)
|
||||
* @files files_struct for process
|
||||
* (protected by @files_lock)
|
||||
* @files_lock mutex to protect @files
|
||||
* @deferred_work_node: element for binder_deferred_list
|
||||
* (protected by binder_deferred_lock)
|
||||
* @deferred_work: bitmap of deferred work to perform
|
||||
|
@ -527,8 +524,6 @@ struct binder_proc {
|
|||
struct list_head waiting_threads;
|
||||
int pid;
|
||||
struct task_struct *tsk;
|
||||
struct files_struct *files;
|
||||
struct mutex files_lock;
|
||||
struct hlist_node deferred_work_node;
|
||||
int deferred_work;
|
||||
bool is_dead;
|
||||
|
@ -611,6 +606,23 @@ struct binder_thread {
|
|||
bool is_dead;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct binder_txn_fd_fixup - transaction fd fixup list element
|
||||
* @fixup_entry: list entry
|
||||
* @file: struct file to be associated with new fd
|
||||
* @offset: offset in buffer data to this fixup
|
||||
*
|
||||
* List element for fd fixups in a transaction. Since file
|
||||
* descriptors need to be allocated in the context of the
|
||||
* target process, we pass each fd to be processed in this
|
||||
* struct.
|
||||
*/
|
||||
struct binder_txn_fd_fixup {
|
||||
struct list_head fixup_entry;
|
||||
struct file *file;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
struct binder_transaction {
|
||||
int debug_id;
|
||||
struct binder_work work;
|
||||
|
@ -628,6 +640,7 @@ struct binder_transaction {
|
|||
long priority;
|
||||
long saved_priority;
|
||||
kuid_t sender_euid;
|
||||
struct list_head fd_fixups;
|
||||
/**
|
||||
* @lock: protects @from, @to_proc, and @to_thread
|
||||
*
|
||||
|
@ -822,6 +835,7 @@ static void
|
|||
binder_enqueue_deferred_thread_work_ilocked(struct binder_thread *thread,
|
||||
struct binder_work *work)
|
||||
{
|
||||
WARN_ON(!list_empty(&thread->waiting_thread_node));
|
||||
binder_enqueue_work_ilocked(work, &thread->todo);
|
||||
}
|
||||
|
||||
|
@ -839,6 +853,7 @@ static void
|
|||
binder_enqueue_thread_work_ilocked(struct binder_thread *thread,
|
||||
struct binder_work *work)
|
||||
{
|
||||
WARN_ON(!list_empty(&thread->waiting_thread_node));
|
||||
binder_enqueue_work_ilocked(work, &thread->todo);
|
||||
thread->process_todo = true;
|
||||
}
|
||||
|
@ -920,66 +935,6 @@ static void binder_free_thread(struct binder_thread *thread);
|
|||
static void binder_free_proc(struct binder_proc *proc);
|
||||
static void binder_inc_node_tmpref_ilocked(struct binder_node *node);
|
||||
|
||||
static int task_get_unused_fd_flags(struct binder_proc *proc, int flags)
|
||||
{
|
||||
unsigned long rlim_cur;
|
||||
unsigned long irqs;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&proc->files_lock);
|
||||
if (proc->files == NULL) {
|
||||
ret = -ESRCH;
|
||||
goto err;
|
||||
}
|
||||
if (!lock_task_sighand(proc->tsk, &irqs)) {
|
||||
ret = -EMFILE;
|
||||
goto err;
|
||||
}
|
||||
rlim_cur = task_rlimit(proc->tsk, RLIMIT_NOFILE);
|
||||
unlock_task_sighand(proc->tsk, &irqs);
|
||||
|
||||
ret = __alloc_fd(proc->files, 0, rlim_cur, flags);
|
||||
err:
|
||||
mutex_unlock(&proc->files_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* copied from fd_install
|
||||
*/
|
||||
static void task_fd_install(
|
||||
struct binder_proc *proc, unsigned int fd, struct file *file)
|
||||
{
|
||||
mutex_lock(&proc->files_lock);
|
||||
if (proc->files)
|
||||
__fd_install(proc->files, fd, file);
|
||||
mutex_unlock(&proc->files_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* copied from sys_close
|
||||
*/
|
||||
static long task_close_fd(struct binder_proc *proc, unsigned int fd)
|
||||
{
|
||||
int retval;
|
||||
|
||||
mutex_lock(&proc->files_lock);
|
||||
if (proc->files == NULL) {
|
||||
retval = -ESRCH;
|
||||
goto err;
|
||||
}
|
||||
retval = __close_fd(proc->files, fd);
|
||||
/* can't restart close syscall because file table entry was cleared */
|
||||
if (unlikely(retval == -ERESTARTSYS ||
|
||||
retval == -ERESTARTNOINTR ||
|
||||
retval == -ERESTARTNOHAND ||
|
||||
retval == -ERESTART_RESTARTBLOCK))
|
||||
retval = -EINTR;
|
||||
err:
|
||||
mutex_unlock(&proc->files_lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static bool binder_has_work_ilocked(struct binder_thread *thread,
|
||||
bool do_proc_work)
|
||||
{
|
||||
|
@ -1270,19 +1225,12 @@ static int binder_inc_node_nilocked(struct binder_node *node, int strong,
|
|||
} else
|
||||
node->local_strong_refs++;
|
||||
if (!node->has_strong_ref && target_list) {
|
||||
struct binder_thread *thread = container_of(target_list,
|
||||
struct binder_thread, todo);
|
||||
binder_dequeue_work_ilocked(&node->work);
|
||||
/*
|
||||
* Note: this function is the only place where we queue
|
||||
* directly to a thread->todo without using the
|
||||
* corresponding binder_enqueue_thread_work() helper
|
||||
* functions; in this case it's ok to not set the
|
||||
* process_todo flag, since we know this node work will
|
||||
* always be followed by other work that starts queue
|
||||
* processing: in case of synchronous transactions, a
|
||||
* BR_REPLY or BR_ERROR; in case of oneway
|
||||
* transactions, a BR_TRANSACTION_COMPLETE.
|
||||
*/
|
||||
binder_enqueue_work_ilocked(&node->work, target_list);
|
||||
BUG_ON(&thread->todo != target_list);
|
||||
binder_enqueue_deferred_thread_work_ilocked(thread,
|
||||
&node->work);
|
||||
}
|
||||
} else {
|
||||
if (!internal)
|
||||
|
@ -1958,10 +1906,32 @@ static struct binder_thread *binder_get_txn_from_and_acq_inner(
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* binder_free_txn_fixups() - free unprocessed fd fixups
|
||||
* @t: binder transaction for t->from
|
||||
*
|
||||
* If the transaction is being torn down prior to being
|
||||
* processed by the target process, free all of the
|
||||
* fd fixups and fput the file structs. It is safe to
|
||||
* call this function after the fixups have been
|
||||
* processed -- in that case, the list will be empty.
|
||||
*/
|
||||
static void binder_free_txn_fixups(struct binder_transaction *t)
|
||||
{
|
||||
struct binder_txn_fd_fixup *fixup, *tmp;
|
||||
|
||||
list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) {
|
||||
fput(fixup->file);
|
||||
list_del(&fixup->fixup_entry);
|
||||
kfree(fixup);
|
||||
}
|
||||
}
|
||||
|
||||
static void binder_free_transaction(struct binder_transaction *t)
|
||||
{
|
||||
if (t->buffer)
|
||||
t->buffer->transaction = NULL;
|
||||
binder_free_txn_fixups(t);
|
||||
kfree(t);
|
||||
binder_stats_deleted(BINDER_STAT_TRANSACTION);
|
||||
}
|
||||
|
@ -2262,12 +2232,17 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,
|
|||
} break;
|
||||
|
||||
case BINDER_TYPE_FD: {
|
||||
struct binder_fd_object *fp = to_binder_fd_object(hdr);
|
||||
|
||||
binder_debug(BINDER_DEBUG_TRANSACTION,
|
||||
" fd %d\n", fp->fd);
|
||||
if (failed_at)
|
||||
task_close_fd(proc, fp->fd);
|
||||
/*
|
||||
* No need to close the file here since user-space
|
||||
* closes it for for successfully delivered
|
||||
* transactions. For transactions that weren't
|
||||
* delivered, the new fd was never allocated so
|
||||
* there is no need to close and the fput on the
|
||||
* file is done when the transaction is torn
|
||||
* down.
|
||||
*/
|
||||
WARN_ON(failed_at &&
|
||||
proc->tsk == current->group_leader);
|
||||
} break;
|
||||
case BINDER_TYPE_PTR:
|
||||
/*
|
||||
|
@ -2283,6 +2258,15 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,
|
|||
size_t fd_index;
|
||||
binder_size_t fd_buf_size;
|
||||
|
||||
if (proc->tsk != current->group_leader) {
|
||||
/*
|
||||
* Nothing to do if running in sender context
|
||||
* The fd fixups have not been applied so no
|
||||
* fds need to be closed.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
fda = to_binder_fd_array_object(hdr);
|
||||
parent = binder_validate_ptr(buffer, fda->parent,
|
||||
off_start,
|
||||
|
@ -2315,7 +2299,7 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,
|
|||
}
|
||||
fd_array = (u32 *)(parent_buffer + (uintptr_t)fda->parent_offset);
|
||||
for (fd_index = 0; fd_index < fda->num_fds; fd_index++)
|
||||
task_close_fd(proc, fd_array[fd_index]);
|
||||
ksys_close(fd_array[fd_index]);
|
||||
} break;
|
||||
default:
|
||||
pr_err("transaction release %d bad object type %x\n",
|
||||
|
@ -2447,17 +2431,18 @@ done:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int binder_translate_fd(int fd,
|
||||
static int binder_translate_fd(u32 *fdp,
|
||||
struct binder_transaction *t,
|
||||
struct binder_thread *thread,
|
||||
struct binder_transaction *in_reply_to)
|
||||
{
|
||||
struct binder_proc *proc = thread->proc;
|
||||
struct binder_proc *target_proc = t->to_proc;
|
||||
int target_fd;
|
||||
struct binder_txn_fd_fixup *fixup;
|
||||
struct file *file;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
bool target_allows_fd;
|
||||
int fd = *fdp;
|
||||
|
||||
if (in_reply_to)
|
||||
target_allows_fd = !!(in_reply_to->flags & TF_ACCEPT_FDS);
|
||||
|
@ -2485,19 +2470,24 @@ static int binder_translate_fd(int fd,
|
|||
goto err_security;
|
||||
}
|
||||
|
||||
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
|
||||
if (target_fd < 0) {
|
||||
/*
|
||||
* Add fixup record for this transaction. The allocation
|
||||
* of the fd in the target needs to be done from a
|
||||
* target thread.
|
||||
*/
|
||||
fixup = kzalloc(sizeof(*fixup), GFP_KERNEL);
|
||||
if (!fixup) {
|
||||
ret = -ENOMEM;
|
||||
goto err_get_unused_fd;
|
||||
goto err_alloc;
|
||||
}
|
||||
task_fd_install(target_proc, target_fd, file);
|
||||
trace_binder_transaction_fd(t, fd, target_fd);
|
||||
binder_debug(BINDER_DEBUG_TRANSACTION, " fd %d -> %d\n",
|
||||
fd, target_fd);
|
||||
fixup->file = file;
|
||||
fixup->offset = (uintptr_t)fdp - (uintptr_t)t->buffer->data;
|
||||
trace_binder_transaction_fd_send(t, fd, fixup->offset);
|
||||
list_add_tail(&fixup->fixup_entry, &t->fd_fixups);
|
||||
|
||||
return target_fd;
|
||||
return ret;
|
||||
|
||||
err_get_unused_fd:
|
||||
err_alloc:
|
||||
err_security:
|
||||
fput(file);
|
||||
err_fget:
|
||||
|
@ -2511,8 +2501,7 @@ static int binder_translate_fd_array(struct binder_fd_array_object *fda,
|
|||
struct binder_thread *thread,
|
||||
struct binder_transaction *in_reply_to)
|
||||
{
|
||||
binder_size_t fdi, fd_buf_size, num_installed_fds;
|
||||
int target_fd;
|
||||
binder_size_t fdi, fd_buf_size;
|
||||
uintptr_t parent_buffer;
|
||||
u32 *fd_array;
|
||||
struct binder_proc *proc = thread->proc;
|
||||
|
@ -2544,23 +2533,12 @@ static int binder_translate_fd_array(struct binder_fd_array_object *fda,
|
|||
return -EINVAL;
|
||||
}
|
||||
for (fdi = 0; fdi < fda->num_fds; fdi++) {
|
||||
target_fd = binder_translate_fd(fd_array[fdi], t, thread,
|
||||
int ret = binder_translate_fd(&fd_array[fdi], t, thread,
|
||||
in_reply_to);
|
||||
if (target_fd < 0)
|
||||
goto err_translate_fd_failed;
|
||||
fd_array[fdi] = target_fd;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_translate_fd_failed:
|
||||
/*
|
||||
* Failed to allocate fd or security error, free fds
|
||||
* installed so far.
|
||||
*/
|
||||
num_installed_fds = fdi;
|
||||
for (fdi = 0; fdi < num_installed_fds; fdi++)
|
||||
task_close_fd(target_proc, fd_array[fdi]);
|
||||
return target_fd;
|
||||
}
|
||||
|
||||
static int binder_fixup_parent(struct binder_transaction *t,
|
||||
|
@ -2723,6 +2701,7 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
{
|
||||
int ret;
|
||||
struct binder_transaction *t;
|
||||
struct binder_work *w;
|
||||
struct binder_work *tcomplete;
|
||||
binder_size_t *offp, *off_end, *off_start;
|
||||
binder_size_t off_min;
|
||||
|
@ -2864,6 +2843,29 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
goto err_invalid_target_handle;
|
||||
}
|
||||
binder_inner_proc_lock(proc);
|
||||
|
||||
w = list_first_entry_or_null(&thread->todo,
|
||||
struct binder_work, entry);
|
||||
if (!(tr->flags & TF_ONE_WAY) && w &&
|
||||
w->type == BINDER_WORK_TRANSACTION) {
|
||||
/*
|
||||
* Do not allow new outgoing transaction from a
|
||||
* thread that has a transaction at the head of
|
||||
* its todo list. Only need to check the head
|
||||
* because binder_select_thread_ilocked picks a
|
||||
* thread from proc->waiting_threads to enqueue
|
||||
* the transaction, and nothing is queued to the
|
||||
* todo list while the thread is on waiting_threads.
|
||||
*/
|
||||
binder_user_error("%d:%d new transaction not allowed when there is a transaction on thread todo\n",
|
||||
proc->pid, thread->pid);
|
||||
binder_inner_proc_unlock(proc);
|
||||
return_error = BR_FAILED_REPLY;
|
||||
return_error_param = -EPROTO;
|
||||
return_error_line = __LINE__;
|
||||
goto err_bad_todo_list;
|
||||
}
|
||||
|
||||
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
|
||||
struct binder_transaction *tmp;
|
||||
|
||||
|
@ -2911,6 +2913,7 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
return_error_line = __LINE__;
|
||||
goto err_alloc_t_failed;
|
||||
}
|
||||
INIT_LIST_HEAD(&t->fd_fixups);
|
||||
binder_stats_created(BINDER_STAT_TRANSACTION);
|
||||
spin_lock_init(&t->lock);
|
||||
|
||||
|
@ -3066,17 +3069,16 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
|
||||
case BINDER_TYPE_FD: {
|
||||
struct binder_fd_object *fp = to_binder_fd_object(hdr);
|
||||
int target_fd = binder_translate_fd(fp->fd, t, thread,
|
||||
in_reply_to);
|
||||
int ret = binder_translate_fd(&fp->fd, t, thread,
|
||||
in_reply_to);
|
||||
|
||||
if (target_fd < 0) {
|
||||
if (ret < 0) {
|
||||
return_error = BR_FAILED_REPLY;
|
||||
return_error_param = target_fd;
|
||||
return_error_param = ret;
|
||||
return_error_line = __LINE__;
|
||||
goto err_translate_failed;
|
||||
}
|
||||
fp->pad_binder = 0;
|
||||
fp->fd = target_fd;
|
||||
} break;
|
||||
case BINDER_TYPE_FDA: {
|
||||
struct binder_fd_array_object *fda =
|
||||
|
@ -3233,6 +3235,7 @@ err_bad_object_type:
|
|||
err_bad_offset:
|
||||
err_bad_parent:
|
||||
err_copy_data_failed:
|
||||
binder_free_txn_fixups(t);
|
||||
trace_binder_transaction_failed_buffer_release(t->buffer);
|
||||
binder_transaction_buffer_release(target_proc, t->buffer, offp);
|
||||
if (target_node)
|
||||
|
@ -3247,6 +3250,7 @@ err_alloc_tcomplete_failed:
|
|||
kfree(t);
|
||||
binder_stats_deleted(BINDER_STAT_TRANSACTION);
|
||||
err_alloc_t_failed:
|
||||
err_bad_todo_list:
|
||||
err_bad_call_stack:
|
||||
err_empty_call_stack:
|
||||
err_dead_binder:
|
||||
|
@ -3294,6 +3298,47 @@ err_invalid_target_handle:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* binder_free_buf() - free the specified buffer
|
||||
* @proc: binder proc that owns buffer
|
||||
* @buffer: buffer to be freed
|
||||
*
|
||||
* If buffer for an async transaction, enqueue the next async
|
||||
* transaction from the node.
|
||||
*
|
||||
* Cleanup buffer and free it.
|
||||
*/
|
||||
static void
|
||||
binder_free_buf(struct binder_proc *proc, struct binder_buffer *buffer)
|
||||
{
|
||||
if (buffer->transaction) {
|
||||
buffer->transaction->buffer = NULL;
|
||||
buffer->transaction = NULL;
|
||||
}
|
||||
if (buffer->async_transaction && buffer->target_node) {
|
||||
struct binder_node *buf_node;
|
||||
struct binder_work *w;
|
||||
|
||||
buf_node = buffer->target_node;
|
||||
binder_node_inner_lock(buf_node);
|
||||
BUG_ON(!buf_node->has_async_transaction);
|
||||
BUG_ON(buf_node->proc != proc);
|
||||
w = binder_dequeue_work_head_ilocked(
|
||||
&buf_node->async_todo);
|
||||
if (!w) {
|
||||
buf_node->has_async_transaction = false;
|
||||
} else {
|
||||
binder_enqueue_work_ilocked(
|
||||
w, &proc->todo);
|
||||
binder_wakeup_proc_ilocked(proc);
|
||||
}
|
||||
binder_node_inner_unlock(buf_node);
|
||||
}
|
||||
trace_binder_transaction_buffer_release(buffer);
|
||||
binder_transaction_buffer_release(proc, buffer, NULL);
|
||||
binder_alloc_free_buf(&proc->alloc, buffer);
|
||||
}
|
||||
|
||||
static int binder_thread_write(struct binder_proc *proc,
|
||||
struct binder_thread *thread,
|
||||
binder_uintptr_t binder_buffer, size_t size,
|
||||
|
@ -3480,33 +3525,7 @@ static int binder_thread_write(struct binder_proc *proc,
|
|||
proc->pid, thread->pid, (u64)data_ptr,
|
||||
buffer->debug_id,
|
||||
buffer->transaction ? "active" : "finished");
|
||||
|
||||
if (buffer->transaction) {
|
||||
buffer->transaction->buffer = NULL;
|
||||
buffer->transaction = NULL;
|
||||
}
|
||||
if (buffer->async_transaction && buffer->target_node) {
|
||||
struct binder_node *buf_node;
|
||||
struct binder_work *w;
|
||||
|
||||
buf_node = buffer->target_node;
|
||||
binder_node_inner_lock(buf_node);
|
||||
BUG_ON(!buf_node->has_async_transaction);
|
||||
BUG_ON(buf_node->proc != proc);
|
||||
w = binder_dequeue_work_head_ilocked(
|
||||
&buf_node->async_todo);
|
||||
if (!w) {
|
||||
buf_node->has_async_transaction = false;
|
||||
} else {
|
||||
binder_enqueue_work_ilocked(
|
||||
w, &proc->todo);
|
||||
binder_wakeup_proc_ilocked(proc);
|
||||
}
|
||||
binder_node_inner_unlock(buf_node);
|
||||
}
|
||||
trace_binder_transaction_buffer_release(buffer);
|
||||
binder_transaction_buffer_release(proc, buffer, NULL);
|
||||
binder_alloc_free_buf(&proc->alloc, buffer);
|
||||
binder_free_buf(proc, buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3829,6 +3848,76 @@ static int binder_wait_for_work(struct binder_thread *thread,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* binder_apply_fd_fixups() - finish fd translation
|
||||
* @t: binder transaction with list of fd fixups
|
||||
*
|
||||
* Now that we are in the context of the transaction target
|
||||
* process, we can allocate and install fds. Process the
|
||||
* list of fds to translate and fixup the buffer with the
|
||||
* new fds.
|
||||
*
|
||||
* If we fail to allocate an fd, then free the resources by
|
||||
* fput'ing files that have not been processed and ksys_close'ing
|
||||
* any fds that have already been allocated.
|
||||
*/
|
||||
static int binder_apply_fd_fixups(struct binder_transaction *t)
|
||||
{
|
||||
struct binder_txn_fd_fixup *fixup, *tmp;
|
||||
int ret = 0;
|
||||
|
||||
list_for_each_entry(fixup, &t->fd_fixups, fixup_entry) {
|
||||
int fd = get_unused_fd_flags(O_CLOEXEC);
|
||||
u32 *fdp;
|
||||
|
||||
if (fd < 0) {
|
||||
binder_debug(BINDER_DEBUG_TRANSACTION,
|
||||
"failed fd fixup txn %d fd %d\n",
|
||||
t->debug_id, fd);
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
binder_debug(BINDER_DEBUG_TRANSACTION,
|
||||
"fd fixup txn %d fd %d\n",
|
||||
t->debug_id, fd);
|
||||
trace_binder_transaction_fd_recv(t, fd, fixup->offset);
|
||||
fd_install(fd, fixup->file);
|
||||
fixup->file = NULL;
|
||||
fdp = (u32 *)(t->buffer->data + fixup->offset);
|
||||
/*
|
||||
* This store can cause problems for CPUs with a
|
||||
* VIVT cache (eg ARMv5) since the cache cannot
|
||||
* detect virtual aliases to the same physical cacheline.
|
||||
* To support VIVT, this address and the user-space VA
|
||||
* would both need to be flushed. Since this kernel
|
||||
* VA is not constructed via page_to_virt(), we can't
|
||||
* use flush_dcache_page() on it, so we'd have to use
|
||||
* an internal function. If devices with VIVT ever
|
||||
* need to run Android, we'll either need to go back
|
||||
* to patching the translated fd from the sender side
|
||||
* (using the non-standard kernel functions), or rework
|
||||
* how the kernel uses the buffer to use page_to_virt()
|
||||
* addresses instead of allocating in our own vm area.
|
||||
*
|
||||
* For now, we disable compilation if CONFIG_CPU_CACHE_VIVT.
|
||||
*/
|
||||
*fdp = fd;
|
||||
}
|
||||
list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) {
|
||||
if (fixup->file) {
|
||||
fput(fixup->file);
|
||||
} else if (ret) {
|
||||
u32 *fdp = (u32 *)(t->buffer->data + fixup->offset);
|
||||
|
||||
ksys_close(*fdp);
|
||||
}
|
||||
list_del(&fixup->fixup_entry);
|
||||
kfree(fixup);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int binder_thread_read(struct binder_proc *proc,
|
||||
struct binder_thread *thread,
|
||||
binder_uintptr_t binder_buffer, size_t size,
|
||||
|
@ -4110,6 +4199,34 @@ retry:
|
|||
tr.sender_pid = 0;
|
||||
}
|
||||
|
||||
ret = binder_apply_fd_fixups(t);
|
||||
if (ret) {
|
||||
struct binder_buffer *buffer = t->buffer;
|
||||
bool oneway = !!(t->flags & TF_ONE_WAY);
|
||||
int tid = t->debug_id;
|
||||
|
||||
if (t_from)
|
||||
binder_thread_dec_tmpref(t_from);
|
||||
buffer->transaction = NULL;
|
||||
binder_cleanup_transaction(t, "fd fixups failed",
|
||||
BR_FAILED_REPLY);
|
||||
binder_free_buf(proc, buffer);
|
||||
binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
|
||||
"%d:%d %stransaction %d fd fixups failed %d/%d, line %d\n",
|
||||
proc->pid, thread->pid,
|
||||
oneway ? "async " :
|
||||
(cmd == BR_REPLY ? "reply " : ""),
|
||||
tid, BR_FAILED_REPLY, ret, __LINE__);
|
||||
if (cmd == BR_REPLY) {
|
||||
cmd = BR_FAILED_REPLY;
|
||||
if (put_user(cmd, (uint32_t __user *)ptr))
|
||||
return -EFAULT;
|
||||
ptr += sizeof(uint32_t);
|
||||
binder_stat_br(proc, thread, cmd);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
tr.data_size = t->buffer->data_size;
|
||||
tr.offsets_size = t->buffer->offsets_size;
|
||||
tr.data.ptr.buffer = (binder_uintptr_t)
|
||||
|
@ -4544,6 +4661,42 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int binder_ioctl_get_node_info_for_ref(struct binder_proc *proc,
|
||||
struct binder_node_info_for_ref *info)
|
||||
{
|
||||
struct binder_node *node;
|
||||
struct binder_context *context = proc->context;
|
||||
__u32 handle = info->handle;
|
||||
|
||||
if (info->strong_count || info->weak_count || info->reserved1 ||
|
||||
info->reserved2 || info->reserved3) {
|
||||
binder_user_error("%d BINDER_GET_NODE_INFO_FOR_REF: only handle may be non-zero.",
|
||||
proc->pid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* This ioctl may only be used by the context manager */
|
||||
mutex_lock(&context->context_mgr_node_lock);
|
||||
if (!context->binder_context_mgr_node ||
|
||||
context->binder_context_mgr_node->proc != proc) {
|
||||
mutex_unlock(&context->context_mgr_node_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
mutex_unlock(&context->context_mgr_node_lock);
|
||||
|
||||
node = binder_get_node_from_ref(proc, handle, true, NULL);
|
||||
if (!node)
|
||||
return -EINVAL;
|
||||
|
||||
info->strong_count = node->local_strong_refs +
|
||||
node->internal_strong_refs;
|
||||
info->weak_count = node->local_weak_refs;
|
||||
|
||||
binder_put_node(node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int binder_ioctl_get_node_debug_info(struct binder_proc *proc,
|
||||
struct binder_node_debug_info *info)
|
||||
{
|
||||
|
@ -4638,6 +4791,25 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||
}
|
||||
break;
|
||||
}
|
||||
case BINDER_GET_NODE_INFO_FOR_REF: {
|
||||
struct binder_node_info_for_ref info;
|
||||
|
||||
if (copy_from_user(&info, ubuf, sizeof(info))) {
|
||||
ret = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = binder_ioctl_get_node_info_for_ref(proc, &info);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (copy_to_user(ubuf, &info, sizeof(info))) {
|
||||
ret = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case BINDER_GET_NODE_DEBUG_INFO: {
|
||||
struct binder_node_debug_info info;
|
||||
|
||||
|
@ -4693,7 +4865,6 @@ static void binder_vma_close(struct vm_area_struct *vma)
|
|||
(vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
|
||||
(unsigned long)pgprot_val(vma->vm_page_prot));
|
||||
binder_alloc_vma_close(&proc->alloc);
|
||||
binder_defer_work(proc, BINDER_DEFERRED_PUT_FILES);
|
||||
}
|
||||
|
||||
static vm_fault_t binder_vm_fault(struct vm_fault *vmf)
|
||||
|
@ -4739,9 +4910,6 @@ static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
|
|||
ret = binder_alloc_mmap_handler(&proc->alloc, vma);
|
||||
if (ret)
|
||||
return ret;
|
||||
mutex_lock(&proc->files_lock);
|
||||
proc->files = get_files_struct(current);
|
||||
mutex_unlock(&proc->files_lock);
|
||||
return 0;
|
||||
|
||||
err_bad_arg:
|
||||
|
@ -4765,7 +4933,6 @@ static int binder_open(struct inode *nodp, struct file *filp)
|
|||
spin_lock_init(&proc->outer_lock);
|
||||
get_task_struct(current->group_leader);
|
||||
proc->tsk = current->group_leader;
|
||||
mutex_init(&proc->files_lock);
|
||||
INIT_LIST_HEAD(&proc->todo);
|
||||
proc->default_priority = task_nice(current);
|
||||
binder_dev = container_of(filp->private_data, struct binder_device,
|
||||
|
@ -4915,8 +5082,6 @@ static void binder_deferred_release(struct binder_proc *proc)
|
|||
struct rb_node *n;
|
||||
int threads, nodes, incoming_refs, outgoing_refs, active_transactions;
|
||||
|
||||
BUG_ON(proc->files);
|
||||
|
||||
mutex_lock(&binder_procs_lock);
|
||||
hlist_del(&proc->proc_node);
|
||||
mutex_unlock(&binder_procs_lock);
|
||||
|
@ -4998,7 +5163,6 @@ static void binder_deferred_release(struct binder_proc *proc)
|
|||
static void binder_deferred_func(struct work_struct *work)
|
||||
{
|
||||
struct binder_proc *proc;
|
||||
struct files_struct *files;
|
||||
|
||||
int defer;
|
||||
|
||||
|
@ -5016,23 +5180,11 @@ static void binder_deferred_func(struct work_struct *work)
|
|||
}
|
||||
mutex_unlock(&binder_deferred_lock);
|
||||
|
||||
files = NULL;
|
||||
if (defer & BINDER_DEFERRED_PUT_FILES) {
|
||||
mutex_lock(&proc->files_lock);
|
||||
files = proc->files;
|
||||
if (files)
|
||||
proc->files = NULL;
|
||||
mutex_unlock(&proc->files_lock);
|
||||
}
|
||||
|
||||
if (defer & BINDER_DEFERRED_FLUSH)
|
||||
binder_deferred_flush(proc);
|
||||
|
||||
if (defer & BINDER_DEFERRED_RELEASE)
|
||||
binder_deferred_release(proc); /* frees proc */
|
||||
|
||||
if (files)
|
||||
put_files_struct(files);
|
||||
} while (proc);
|
||||
}
|
||||
static DECLARE_WORK(binder_deferred_work, binder_deferred_func);
|
||||
|
@ -5667,12 +5819,11 @@ static int __init binder_init(void)
|
|||
* Copy the module_parameter string, because we don't want to
|
||||
* tokenize it in-place.
|
||||
*/
|
||||
device_names = kzalloc(strlen(binder_devices_param) + 1, GFP_KERNEL);
|
||||
device_names = kstrdup(binder_devices_param, GFP_KERNEL);
|
||||
if (!device_names) {
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc_device_names_failed;
|
||||
}
|
||||
strcpy(device_names, binder_devices_param);
|
||||
|
||||
device_tmp = device_names;
|
||||
while ((device_name = strsep(&device_tmp, ","))) {
|
||||
|
|
|
@ -223,22 +223,40 @@ TRACE_EVENT(binder_transaction_ref_to_ref,
|
|||
__entry->dest_ref_debug_id, __entry->dest_ref_desc)
|
||||
);
|
||||
|
||||
TRACE_EVENT(binder_transaction_fd,
|
||||
TP_PROTO(struct binder_transaction *t, int src_fd, int dest_fd),
|
||||
TP_ARGS(t, src_fd, dest_fd),
|
||||
TRACE_EVENT(binder_transaction_fd_send,
|
||||
TP_PROTO(struct binder_transaction *t, int fd, size_t offset),
|
||||
TP_ARGS(t, fd, offset),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field(int, debug_id)
|
||||
__field(int, src_fd)
|
||||
__field(int, dest_fd)
|
||||
__field(int, fd)
|
||||
__field(size_t, offset)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->debug_id = t->debug_id;
|
||||
__entry->src_fd = src_fd;
|
||||
__entry->dest_fd = dest_fd;
|
||||
__entry->fd = fd;
|
||||
__entry->offset = offset;
|
||||
),
|
||||
TP_printk("transaction=%d src_fd=%d ==> dest_fd=%d",
|
||||
__entry->debug_id, __entry->src_fd, __entry->dest_fd)
|
||||
TP_printk("transaction=%d src_fd=%d offset=%zu",
|
||||
__entry->debug_id, __entry->fd, __entry->offset)
|
||||
);
|
||||
|
||||
TRACE_EVENT(binder_transaction_fd_recv,
|
||||
TP_PROTO(struct binder_transaction *t, int fd, size_t offset),
|
||||
TP_ARGS(t, fd, offset),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field(int, debug_id)
|
||||
__field(int, fd)
|
||||
__field(size_t, offset)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->debug_id = t->debug_id;
|
||||
__entry->fd = fd;
|
||||
__entry->offset = offset;
|
||||
),
|
||||
TP_printk("transaction=%d dest_fd=%d offset=%zu",
|
||||
__entry->debug_id, __entry->fd, __entry->offset)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(binder_buffer_class,
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
|
||||
* Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
|
||||
*
|
||||
* Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
|
||||
* Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/extcon-provider.h>
|
||||
|
@ -32,10 +24,10 @@
|
|||
#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1)
|
||||
#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2)
|
||||
#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3)
|
||||
#define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4)
|
||||
#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5)
|
||||
#define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6)
|
||||
#define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7)
|
||||
#define CHT_WC_CHGRCTRL0_TTLCK BIT(4)
|
||||
#define CHT_WC_CHGRCTRL0_CCSM_OFF BIT(5)
|
||||
#define CHT_WC_CHGRCTRL0_DBPOFF BIT(6)
|
||||
#define CHT_WC_CHGRCTRL0_CHR_WDT_NOKICK BIT(7)
|
||||
|
||||
#define CHT_WC_CHGRCTRL1 0x5e17
|
||||
|
||||
|
@ -52,7 +44,7 @@
|
|||
#define CHT_WC_USBSRC_TYPE_ACA 4
|
||||
#define CHT_WC_USBSRC_TYPE_SE1 5
|
||||
#define CHT_WC_USBSRC_TYPE_MHL 6
|
||||
#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7
|
||||
#define CHT_WC_USBSRC_TYPE_FLOATING 7
|
||||
#define CHT_WC_USBSRC_TYPE_OTHER 8
|
||||
#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9
|
||||
|
||||
|
@ -61,9 +53,12 @@
|
|||
#define CHT_WC_PWRSRC_STS 0x6e1e
|
||||
#define CHT_WC_PWRSRC_VBUS BIT(0)
|
||||
#define CHT_WC_PWRSRC_DC BIT(1)
|
||||
#define CHT_WC_PWRSRC_BAT BIT(2)
|
||||
#define CHT_WC_PWRSRC_ID_GND BIT(3)
|
||||
#define CHT_WC_PWRSRC_ID_FLOAT BIT(4)
|
||||
#define CHT_WC_PWRSRC_BATT BIT(2)
|
||||
#define CHT_WC_PWRSRC_USBID_MASK GENMASK(4, 3)
|
||||
#define CHT_WC_PWRSRC_USBID_SHIFT 3
|
||||
#define CHT_WC_PWRSRC_RID_ACA 0
|
||||
#define CHT_WC_PWRSRC_RID_GND 1
|
||||
#define CHT_WC_PWRSRC_RID_FLOAT 2
|
||||
|
||||
#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d
|
||||
#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0)
|
||||
|
@ -104,16 +99,20 @@ struct cht_wc_extcon_data {
|
|||
|
||||
static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
|
||||
{
|
||||
if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND)
|
||||
switch ((pwrsrc_sts & CHT_WC_PWRSRC_USBID_MASK) >> CHT_WC_PWRSRC_USBID_SHIFT) {
|
||||
case CHT_WC_PWRSRC_RID_GND:
|
||||
return USB_ID_GND;
|
||||
if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT)
|
||||
case CHT_WC_PWRSRC_RID_FLOAT:
|
||||
return USB_ID_FLOAT;
|
||||
|
||||
/*
|
||||
* Once we have iio support for the gpadc we should read the USBID
|
||||
* gpadc channel here and determine ACA role based on that.
|
||||
*/
|
||||
return USB_ID_FLOAT;
|
||||
case CHT_WC_PWRSRC_RID_ACA:
|
||||
default:
|
||||
/*
|
||||
* Once we have IIO support for the GPADC we should read
|
||||
* the USBID GPADC channel here and determine ACA role
|
||||
* based on that.
|
||||
*/
|
||||
return USB_ID_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
|
||||
|
@ -156,9 +155,9 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
|
|||
dev_warn(ext->dev,
|
||||
"Unhandled charger type %d, defaulting to SDP\n",
|
||||
ret);
|
||||
/* Fall through, treat as SDP */
|
||||
return EXTCON_CHG_USB_SDP;
|
||||
case CHT_WC_USBSRC_TYPE_SDP:
|
||||
case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN:
|
||||
case CHT_WC_USBSRC_TYPE_FLOATING:
|
||||
case CHT_WC_USBSRC_TYPE_OTHER:
|
||||
return EXTCON_CHG_USB_SDP;
|
||||
case CHT_WC_USBSRC_TYPE_CDP:
|
||||
|
@ -279,7 +278,7 @@ static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
|
|||
{
|
||||
int ret, mask, val;
|
||||
|
||||
mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK;
|
||||
mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF;
|
||||
val = enable ? mask : 0;
|
||||
ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
|
||||
if (ret)
|
||||
|
@ -292,6 +291,7 @@ static int cht_wc_extcon_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
|
||||
struct cht_wc_extcon_data *ext;
|
||||
unsigned long mask = ~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_USBID_MASK);
|
||||
int irq, ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
|
@ -352,9 +352,7 @@ static int cht_wc_extcon_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
/* Unmask irqs */
|
||||
ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK,
|
||||
(int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND |
|
||||
CHT_WC_PWRSRC_ID_FLOAT));
|
||||
ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, mask);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error writing irq-mask: %d\n", ret);
|
||||
goto disable_sw_control;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel INT3496 ACPI device extcon driver
|
||||
*
|
||||
|
@ -7,15 +8,6 @@
|
|||
*
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
* Author: David Cohen <david.a.cohen@linux.intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
@ -192,4 +184,4 @@ module_platform_driver(int3496_driver);
|
|||
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_DESCRIPTION("Intel INT3496 ACPI device extcon driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
/*
|
||||
* extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC
|
||||
*
|
||||
* Copyright (C) 2013,2014 Samsung Electronics
|
||||
* Chanwoo Choi <cw00.choi@samsung.com>
|
||||
* Krzysztof Kozlowski <krzk@kernel.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
//
|
||||
// extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC
|
||||
//
|
||||
// Copyright (C) 2013,2014 Samsung Electronics
|
||||
// Chanwoo Choi <cw00.choi@samsung.com>
|
||||
// Krzysztof Kozlowski <krzk@kernel.org>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
/*
|
||||
* extcon-max77693.c - MAX77693 extcon driver to support MAX77693 MUIC
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electrnoics
|
||||
* Chanwoo Choi <cw00.choi@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
//
|
||||
// extcon-max77693.c - MAX77693 extcon driver to support MAX77693 MUIC
|
||||
//
|
||||
// Copyright (C) 2012 Samsung Electrnoics
|
||||
// Chanwoo Choi <cw00.choi@samsung.com>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
/*
|
||||
* extcon-max77843.c - Maxim MAX77843 extcon driver to support
|
||||
* MUIC(Micro USB Interface Controller)
|
||||
*
|
||||
* Copyright (C) 2015 Samsung Electronics
|
||||
* Author: Jaewon Kim <jaewon02.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
//
|
||||
// extcon-max77843.c - Maxim MAX77843 extcon driver to support
|
||||
// MUIC(Micro USB Interface Controller)
|
||||
//
|
||||
// Copyright (C) 2015 Samsung Electronics
|
||||
// Author: Jaewon Kim <jaewon02.kim@samsung.com>
|
||||
|
||||
#include <linux/extcon-provider.h>
|
||||
#include <linux/i2c.h>
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
/*
|
||||
* extcon-max8997.c - MAX8997 extcon driver to support MAX8997 MUIC
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
//
|
||||
// extcon-max8997.c - MAX8997 extcon driver to support MAX8997 MUIC
|
||||
//
|
||||
// Copyright (C) 2012 Samsung Electronics
|
||||
// Donggeun Kim <dg77.kim@samsung.com>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
|
|
|
@ -628,7 +628,7 @@ int extcon_get_property(struct extcon_dev *edev, unsigned int id,
|
|||
unsigned long flags;
|
||||
int index, ret = 0;
|
||||
|
||||
*prop_val = (union extcon_property_value)(0);
|
||||
*prop_val = (union extcon_property_value){0};
|
||||
|
||||
if (!edev)
|
||||
return -EINVAL;
|
||||
|
@ -1123,7 +1123,6 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
(unsigned long)atomic_inc_return(&edev_no));
|
||||
|
||||
if (edev->max_supported) {
|
||||
char buf[10];
|
||||
char *str;
|
||||
struct extcon_cable *cable;
|
||||
|
||||
|
@ -1137,9 +1136,7 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
for (index = 0; index < edev->max_supported; index++) {
|
||||
cable = &edev->cables[index];
|
||||
|
||||
snprintf(buf, 10, "cable.%d", index);
|
||||
str = kzalloc(strlen(buf) + 1,
|
||||
GFP_KERNEL);
|
||||
str = kasprintf(GFP_KERNEL, "cable.%d", index);
|
||||
if (!str) {
|
||||
for (index--; index >= 0; index--) {
|
||||
cable = &edev->cables[index];
|
||||
|
@ -1149,7 +1146,6 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
|
||||
goto err_alloc_cables;
|
||||
}
|
||||
strcpy(str, buf);
|
||||
|
||||
cable->edev = edev;
|
||||
cable->cable_index = index;
|
||||
|
@ -1172,7 +1168,6 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
}
|
||||
|
||||
if (edev->max_supported && edev->mutually_exclusive) {
|
||||
char buf[80];
|
||||
char *name;
|
||||
|
||||
/* Count the size of mutually_exclusive array */
|
||||
|
@ -1197,9 +1192,8 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
}
|
||||
|
||||
for (index = 0; edev->mutually_exclusive[index]; index++) {
|
||||
sprintf(buf, "0x%x", edev->mutually_exclusive[index]);
|
||||
name = kzalloc(strlen(buf) + 1,
|
||||
GFP_KERNEL);
|
||||
name = kasprintf(GFP_KERNEL, "0x%x",
|
||||
edev->mutually_exclusive[index]);
|
||||
if (!name) {
|
||||
for (index--; index >= 0; index--) {
|
||||
kfree(edev->d_attrs_muex[index].attr.
|
||||
|
@ -1210,7 +1204,6 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
ret = -ENOMEM;
|
||||
goto err_muex;
|
||||
}
|
||||
strcpy(name, buf);
|
||||
sysfs_attr_init(&edev->d_attrs_muex[index].attr);
|
||||
edev->d_attrs_muex[index].attr.name = name;
|
||||
edev->d_attrs_muex[index].attr.mode = 0000;
|
||||
|
|
|
@ -10,37 +10,31 @@ if GOOGLE_FIRMWARE
|
|||
|
||||
config GOOGLE_SMI
|
||||
tristate "SMI interface for Google platforms"
|
||||
depends on X86 && ACPI && DMI && EFI
|
||||
select EFI_VARS
|
||||
depends on X86 && ACPI && DMI
|
||||
help
|
||||
Say Y here if you want to enable SMI callbacks for Google
|
||||
platforms. This provides an interface for writing to and
|
||||
clearing the EFI event log and reading and writing NVRAM
|
||||
clearing the event log. If EFI_VARS is also enabled this
|
||||
driver provides an interface for reading and writing NVRAM
|
||||
variables.
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE
|
||||
tristate
|
||||
depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE_ACPI
|
||||
tristate "Coreboot Table Access - ACPI"
|
||||
depends on ACPI
|
||||
select GOOGLE_COREBOOT_TABLE
|
||||
tristate "Coreboot Table Access"
|
||||
depends on ACPI || OF
|
||||
help
|
||||
This option enables the coreboot_table module, which provides other
|
||||
firmware modules to access to the coreboot table. The coreboot table
|
||||
pointer is accessed through the ACPI "GOOGCB00" object.
|
||||
firmware modules access to the coreboot table. The coreboot table
|
||||
pointer is accessed through the ACPI "GOOGCB00" object or the
|
||||
device tree node /firmware/coreboot.
|
||||
If unsure say N.
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE_OF
|
||||
tristate "Coreboot Table Access - Device Tree"
|
||||
depends on OF
|
||||
config GOOGLE_COREBOOT_TABLE_ACPI
|
||||
tristate
|
||||
select GOOGLE_COREBOOT_TABLE
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE_OF
|
||||
tristate
|
||||
select GOOGLE_COREBOOT_TABLE
|
||||
help
|
||||
This option enable the coreboot_table module, which provide other
|
||||
firmware modules to access coreboot table. The coreboot table pointer
|
||||
is accessed through the device tree node /firmware/coreboot.
|
||||
If unsure say N.
|
||||
|
||||
config GOOGLE_MEMCONSOLE
|
||||
tristate
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
obj-$(CONFIG_GOOGLE_SMI) += gsmi.o
|
||||
obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o
|
||||
obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o
|
||||
obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o
|
||||
obj-$(CONFIG_GOOGLE_FRAMEBUFFER_COREBOOT) += framebuffer-coreboot.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* coreboot_table-acpi.c
|
||||
*
|
||||
* Using ACPI to locate Coreboot table and provide coreboot table access.
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
|
||||
static int coreboot_table_acpi_probe(struct platform_device *pdev)
|
||||
{
|
||||
phys_addr_t phyaddr;
|
||||
resource_size_t len;
|
||||
struct coreboot_table_header __iomem *header = NULL;
|
||||
struct resource *res;
|
||||
void __iomem *ptr = NULL;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EINVAL;
|
||||
|
||||
len = resource_size(res);
|
||||
if (!res->start || !len)
|
||||
return -EINVAL;
|
||||
|
||||
phyaddr = res->start;
|
||||
header = ioremap_cache(phyaddr, sizeof(*header));
|
||||
if (header == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ptr = ioremap_cache(phyaddr,
|
||||
header->header_bytes + header->table_bytes);
|
||||
iounmap(header);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
return coreboot_table_init(&pdev->dev, ptr);
|
||||
}
|
||||
|
||||
static int coreboot_table_acpi_remove(struct platform_device *pdev)
|
||||
{
|
||||
return coreboot_table_exit();
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cros_coreboot_acpi_match[] = {
|
||||
{ "GOOGCB00", 0 },
|
||||
{ "BOOT0000", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
|
||||
|
||||
static struct platform_driver coreboot_table_acpi_driver = {
|
||||
.probe = coreboot_table_acpi_probe,
|
||||
.remove = coreboot_table_acpi_remove,
|
||||
.driver = {
|
||||
.name = "coreboot_table_acpi",
|
||||
.acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init coreboot_table_acpi_init(void)
|
||||
{
|
||||
return platform_driver_register(&coreboot_table_acpi_driver);
|
||||
}
|
||||
|
||||
module_init(coreboot_table_acpi_init);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* coreboot_table-of.c
|
||||
*
|
||||
* Coreboot table access through open firmware.
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License v2.0 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
|
||||
static int coreboot_table_of_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *fw_dn = pdev->dev.of_node;
|
||||
void __iomem *ptr;
|
||||
|
||||
ptr = of_iomap(fw_dn, 0);
|
||||
of_node_put(fw_dn);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
return coreboot_table_init(&pdev->dev, ptr);
|
||||
}
|
||||
|
||||
static int coreboot_table_of_remove(struct platform_device *pdev)
|
||||
{
|
||||
return coreboot_table_exit();
|
||||
}
|
||||
|
||||
static const struct of_device_id coreboot_of_match[] = {
|
||||
{ .compatible = "coreboot" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver coreboot_table_of_driver = {
|
||||
.probe = coreboot_table_of_probe,
|
||||
.remove = coreboot_table_of_remove,
|
||||
.driver = {
|
||||
.name = "coreboot_table_of",
|
||||
.of_match_table = coreboot_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init platform_coreboot_table_of_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct device_node *of_node;
|
||||
|
||||
/* Limit device creation to the presence of /firmware/coreboot node */
|
||||
of_node = of_find_node_by_path("/firmware/coreboot");
|
||||
if (!of_node)
|
||||
return -ENODEV;
|
||||
|
||||
if (!of_match_node(coreboot_of_match, of_node))
|
||||
return -ENODEV;
|
||||
|
||||
pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
return platform_driver_register(&coreboot_table_of_driver);
|
||||
}
|
||||
|
||||
module_init(platform_coreboot_table_of_init);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -16,12 +16,15 @@
|
|||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
|
@ -29,8 +32,6 @@
|
|||
#define CB_DEV(d) container_of(d, struct coreboot_device, dev)
|
||||
#define CB_DRV(d) container_of(d, struct coreboot_driver, drv)
|
||||
|
||||
static struct coreboot_table_header __iomem *ptr_header;
|
||||
|
||||
static int coreboot_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct coreboot_device *device = CB_DEV(dev);
|
||||
|
@ -70,12 +71,6 @@ static struct bus_type coreboot_bus_type = {
|
|||
.remove = coreboot_bus_remove,
|
||||
};
|
||||
|
||||
static int __init coreboot_bus_init(void)
|
||||
{
|
||||
return bus_register(&coreboot_bus_type);
|
||||
}
|
||||
module_init(coreboot_bus_init);
|
||||
|
||||
static void coreboot_device_release(struct device *dev)
|
||||
{
|
||||
struct coreboot_device *device = CB_DEV(dev);
|
||||
|
@ -97,62 +92,117 @@ void coreboot_driver_unregister(struct coreboot_driver *driver)
|
|||
}
|
||||
EXPORT_SYMBOL(coreboot_driver_unregister);
|
||||
|
||||
int coreboot_table_init(struct device *dev, void __iomem *ptr)
|
||||
static int coreboot_table_populate(struct device *dev, void *ptr)
|
||||
{
|
||||
int i, ret;
|
||||
void *ptr_entry;
|
||||
struct coreboot_device *device;
|
||||
struct coreboot_table_entry entry;
|
||||
struct coreboot_table_header header;
|
||||
struct coreboot_table_entry *entry;
|
||||
struct coreboot_table_header *header = ptr;
|
||||
|
||||
ptr_header = ptr;
|
||||
memcpy_fromio(&header, ptr_header, sizeof(header));
|
||||
ptr_entry = ptr + header->header_bytes;
|
||||
for (i = 0; i < header->table_entries; i++) {
|
||||
entry = ptr_entry;
|
||||
|
||||
if (strncmp(header.signature, "LBIO", sizeof(header.signature))) {
|
||||
pr_warn("coreboot_table: coreboot table missing or corrupt!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ptr_entry = (void *)ptr_header + header.header_bytes;
|
||||
for (i = 0; i < header.table_entries; i++) {
|
||||
memcpy_fromio(&entry, ptr_entry, sizeof(entry));
|
||||
|
||||
device = kzalloc(sizeof(struct device) + entry.size, GFP_KERNEL);
|
||||
if (!device) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
device = kzalloc(sizeof(struct device) + entry->size, GFP_KERNEL);
|
||||
if (!device)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_name(&device->dev, "coreboot%d", i);
|
||||
device->dev.parent = dev;
|
||||
device->dev.bus = &coreboot_bus_type;
|
||||
device->dev.release = coreboot_device_release;
|
||||
memcpy_fromio(&device->entry, ptr_entry, entry.size);
|
||||
memcpy(&device->entry, ptr_entry, entry->size);
|
||||
|
||||
ret = device_register(&device->dev);
|
||||
if (ret) {
|
||||
put_device(&device->dev);
|
||||
break;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptr_entry += entry.size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_table_init);
|
||||
|
||||
int coreboot_table_exit(void)
|
||||
{
|
||||
if (ptr_header) {
|
||||
bus_unregister(&coreboot_bus_type);
|
||||
iounmap(ptr_header);
|
||||
ptr_header = NULL;
|
||||
ptr_entry += entry->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_table_exit);
|
||||
|
||||
static int coreboot_table_probe(struct platform_device *pdev)
|
||||
{
|
||||
resource_size_t len;
|
||||
struct coreboot_table_header *header;
|
||||
struct resource *res;
|
||||
struct device *dev = &pdev->dev;
|
||||
void *ptr;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EINVAL;
|
||||
|
||||
len = resource_size(res);
|
||||
if (!res->start || !len)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check just the header first to make sure things are sane */
|
||||
header = memremap(res->start, sizeof(*header), MEMREMAP_WB);
|
||||
if (!header)
|
||||
return -ENOMEM;
|
||||
|
||||
len = header->header_bytes + header->table_bytes;
|
||||
ret = strncmp(header->signature, "LBIO", sizeof(header->signature));
|
||||
memunmap(header);
|
||||
if (ret) {
|
||||
dev_warn(dev, "coreboot table missing or corrupt!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ptr = memremap(res->start, len, MEMREMAP_WB);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = bus_register(&coreboot_bus_type);
|
||||
if (!ret) {
|
||||
ret = coreboot_table_populate(dev, ptr);
|
||||
if (ret)
|
||||
bus_unregister(&coreboot_bus_type);
|
||||
}
|
||||
memunmap(ptr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coreboot_table_remove(struct platform_device *pdev)
|
||||
{
|
||||
bus_unregister(&coreboot_bus_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id cros_coreboot_acpi_match[] = {
|
||||
{ "GOOGCB00", 0 },
|
||||
{ "BOOT0000", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id coreboot_of_match[] = {
|
||||
{ .compatible = "coreboot" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, coreboot_of_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver coreboot_table_driver = {
|
||||
.probe = coreboot_table_probe,
|
||||
.remove = coreboot_table_remove,
|
||||
.driver = {
|
||||
.name = "coreboot_table",
|
||||
.acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
|
||||
.of_match_table = of_match_ptr(coreboot_of_match),
|
||||
},
|
||||
};
|
||||
module_platform_driver(coreboot_table_driver);
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -91,10 +91,4 @@ int coreboot_driver_register(struct coreboot_driver *driver);
|
|||
/* Unregister a driver that uses the data from a coreboot table. */
|
||||
void coreboot_driver_unregister(struct coreboot_driver *driver);
|
||||
|
||||
/* Initialize coreboot table module given a pointer to iomem */
|
||||
int coreboot_table_init(struct device *dev, void __iomem *ptr);
|
||||
|
||||
/* Cleanup coreboot table module */
|
||||
int coreboot_table_exit(void);
|
||||
|
||||
#endif /* __COREBOOT_TABLE_H */
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <linux/efi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */
|
||||
/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */
|
||||
|
@ -70,6 +71,8 @@
|
|||
#define GSMI_CMD_SET_NVRAM_VAR 0x03
|
||||
#define GSMI_CMD_SET_EVENT_LOG 0x08
|
||||
#define GSMI_CMD_CLEAR_EVENT_LOG 0x09
|
||||
#define GSMI_CMD_LOG_S0IX_SUSPEND 0x0a
|
||||
#define GSMI_CMD_LOG_S0IX_RESUME 0x0b
|
||||
#define GSMI_CMD_CLEAR_CONFIG 0x20
|
||||
#define GSMI_CMD_HANDSHAKE_TYPE 0xC1
|
||||
|
||||
|
@ -84,7 +87,7 @@ struct gsmi_buf {
|
|||
u32 address; /* physical address of buffer */
|
||||
};
|
||||
|
||||
struct gsmi_device {
|
||||
static struct gsmi_device {
|
||||
struct platform_device *pdev; /* platform device */
|
||||
struct gsmi_buf *name_buf; /* variable name buffer */
|
||||
struct gsmi_buf *data_buf; /* generic data buffer */
|
||||
|
@ -122,7 +125,6 @@ struct gsmi_log_entry_type_1 {
|
|||
u32 instance;
|
||||
} __packed;
|
||||
|
||||
|
||||
/*
|
||||
* Some platforms don't have explicit SMI handshake
|
||||
* and need to wait for SMI to complete.
|
||||
|
@ -133,6 +135,15 @@ module_param(spincount, uint, 0600);
|
|||
MODULE_PARM_DESC(spincount,
|
||||
"The number of loop iterations to use when using the spin handshake.");
|
||||
|
||||
/*
|
||||
* Platforms might not support S0ix logging in their GSMI handlers. In order to
|
||||
* avoid any side-effects of generating an SMI for S0ix logging, use the S0ix
|
||||
* related GSMI commands only for those platforms that explicitly enable this
|
||||
* option.
|
||||
*/
|
||||
static bool s0ix_logging_enable;
|
||||
module_param(s0ix_logging_enable, bool, 0600);
|
||||
|
||||
static struct gsmi_buf *gsmi_buf_alloc(void)
|
||||
{
|
||||
struct gsmi_buf *smibuf;
|
||||
|
@ -289,6 +300,10 @@ static int gsmi_exec(u8 func, u8 sub)
|
|||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI_VARS
|
||||
|
||||
static struct efivars efivars;
|
||||
|
||||
static efi_status_t gsmi_get_variable(efi_char16_t *name,
|
||||
efi_guid_t *vendor, u32 *attr,
|
||||
unsigned long *data_size,
|
||||
|
@ -466,6 +481,8 @@ static const struct efivar_operations efivar_ops = {
|
|||
.get_next_variable = gsmi_get_next_variable,
|
||||
};
|
||||
|
||||
#endif /* CONFIG_EFI_VARS */
|
||||
|
||||
static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
|
@ -480,11 +497,10 @@ static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
|
|||
if (count < sizeof(u32))
|
||||
return -EINVAL;
|
||||
param.type = *(u32 *)buf;
|
||||
count -= sizeof(u32);
|
||||
buf += sizeof(u32);
|
||||
|
||||
/* The remaining buffer is the data payload */
|
||||
if (count > gsmi_dev.data_buf->length)
|
||||
if ((count - sizeof(u32)) > gsmi_dev.data_buf->length)
|
||||
return -EINVAL;
|
||||
param.data_len = count - sizeof(u32);
|
||||
|
||||
|
@ -504,7 +520,7 @@ static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
|
|||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
|
||||
return rc;
|
||||
return (rc == 0) ? count : rc;
|
||||
|
||||
}
|
||||
|
||||
|
@ -716,6 +732,12 @@ static const struct dmi_system_id gsmi_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Coreboot Firmware",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table);
|
||||
|
@ -762,7 +784,6 @@ static __init int gsmi_system_valid(void)
|
|||
}
|
||||
|
||||
static struct kobject *gsmi_kobj;
|
||||
static struct efivars efivars;
|
||||
|
||||
static const struct platform_device_info gsmi_dev_info = {
|
||||
.name = "gsmi",
|
||||
|
@ -771,6 +792,78 @@ static const struct platform_device_info gsmi_dev_info = {
|
|||
.dma_mask = DMA_BIT_MASK(32),
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static void gsmi_log_s0ix_info(u8 cmd)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* If platform has not enabled S0ix logging, then no action is
|
||||
* necessary.
|
||||
*/
|
||||
if (!s0ix_logging_enable)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&gsmi_dev.lock, flags);
|
||||
|
||||
memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length);
|
||||
|
||||
gsmi_exec(GSMI_CALLBACK, cmd);
|
||||
|
||||
spin_unlock_irqrestore(&gsmi_dev.lock, flags);
|
||||
}
|
||||
|
||||
static int gsmi_log_s0ix_suspend(struct device *dev)
|
||||
{
|
||||
/*
|
||||
* If system is not suspending via firmware using the standard ACPI Sx
|
||||
* types, then make a GSMI call to log the suspend info.
|
||||
*/
|
||||
if (!pm_suspend_via_firmware())
|
||||
gsmi_log_s0ix_info(GSMI_CMD_LOG_S0IX_SUSPEND);
|
||||
|
||||
/*
|
||||
* Always return success, since we do not want suspend
|
||||
* to fail just because of logging failure.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gsmi_log_s0ix_resume(struct device *dev)
|
||||
{
|
||||
/*
|
||||
* If system did not resume via firmware, then make a GSMI call to log
|
||||
* the resume info and wake source.
|
||||
*/
|
||||
if (!pm_resume_via_firmware())
|
||||
gsmi_log_s0ix_info(GSMI_CMD_LOG_S0IX_RESUME);
|
||||
|
||||
/*
|
||||
* Always return success, since we do not want resume
|
||||
* to fail just because of logging failure.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops gsmi_pm_ops = {
|
||||
.suspend_noirq = gsmi_log_s0ix_suspend,
|
||||
.resume_noirq = gsmi_log_s0ix_resume,
|
||||
};
|
||||
|
||||
static int gsmi_platform_driver_probe(struct platform_device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver gsmi_driver_info = {
|
||||
.driver = {
|
||||
.name = "gsmi",
|
||||
.pm = &gsmi_pm_ops,
|
||||
},
|
||||
.probe = gsmi_platform_driver_probe,
|
||||
};
|
||||
#endif
|
||||
|
||||
static __init int gsmi_init(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
@ -782,6 +875,14 @@ static __init int gsmi_init(void)
|
|||
|
||||
gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
ret = platform_driver_register(&gsmi_driver_info);
|
||||
if (unlikely(ret)) {
|
||||
printk(KERN_ERR "gsmi: unable to register platform driver\n");
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* register device */
|
||||
gsmi_dev.pdev = platform_device_register_full(&gsmi_dev_info);
|
||||
if (IS_ERR(gsmi_dev.pdev)) {
|
||||
|
@ -886,11 +987,14 @@ static __init int gsmi_init(void)
|
|||
goto out_remove_bin_file;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI_VARS
|
||||
ret = efivars_register(&efivars, &efivar_ops, gsmi_kobj);
|
||||
if (ret) {
|
||||
printk(KERN_INFO "gsmi: Failed to register efivars\n");
|
||||
goto out_remove_sysfs_files;
|
||||
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
|
||||
goto out_remove_bin_file;
|
||||
}
|
||||
#endif
|
||||
|
||||
register_reboot_notifier(&gsmi_reboot_notifier);
|
||||
register_die_notifier(&gsmi_die_notifier);
|
||||
|
@ -901,8 +1005,6 @@ static __init int gsmi_init(void)
|
|||
|
||||
return 0;
|
||||
|
||||
out_remove_sysfs_files:
|
||||
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
|
||||
out_remove_bin_file:
|
||||
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
|
||||
out_err:
|
||||
|
@ -922,7 +1024,9 @@ static void __exit gsmi_exit(void)
|
|||
unregister_die_notifier(&gsmi_die_notifier);
|
||||
atomic_notifier_chain_unregister(&panic_notifier_list,
|
||||
&gsmi_panic_notifier);
|
||||
#ifdef CONFIG_EFI_VARS
|
||||
efivars_unregister(&efivars);
|
||||
#endif
|
||||
|
||||
sysfs_remove_files(gsmi_kobj, gsmi_attrs);
|
||||
sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr);
|
||||
|
|
|
@ -198,7 +198,7 @@ static int vpd_section_init(const char *name, struct vpd_section *sec,
|
|||
|
||||
sec->name = name;
|
||||
|
||||
/* We want to export the raw partion with name ${name}_raw */
|
||||
/* We want to export the raw partition with name ${name}_raw */
|
||||
sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
|
||||
if (!sec->raw_name) {
|
||||
err = -ENOMEM;
|
||||
|
|
|
@ -453,8 +453,8 @@ static int altera_cvp_probe(struct pci_dev *pdev,
|
|||
snprintf(conf->mgr_name, sizeof(conf->mgr_name), "%s @%s",
|
||||
ALTERA_CVP_MGR_NAME, pci_name(pdev));
|
||||
|
||||
mgr = fpga_mgr_create(&pdev->dev, conf->mgr_name,
|
||||
&altera_cvp_ops, conf);
|
||||
mgr = devm_fpga_mgr_create(&pdev->dev, conf->mgr_name,
|
||||
&altera_cvp_ops, conf);
|
||||
if (!mgr) {
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap;
|
||||
|
@ -463,10 +463,8 @@ static int altera_cvp_probe(struct pci_dev *pdev,
|
|||
pci_set_drvdata(pdev, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret) {
|
||||
fpga_mgr_free(mgr);
|
||||
if (ret)
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
ret = driver_create_file(&altera_cvp_driver.driver,
|
||||
&driver_attr_chkcfg);
|
||||
|
|
|
@ -121,18 +121,16 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev)
|
|||
/* Get f2s bridge configuration saved in handoff register */
|
||||
regmap_read(sysmgr, SYSMGR_ISWGRP_HANDOFF3, &priv->mask);
|
||||
|
||||
br = fpga_bridge_create(dev, F2S_BRIDGE_NAME,
|
||||
&altera_fpga2sdram_br_ops, priv);
|
||||
br = devm_fpga_bridge_create(dev, F2S_BRIDGE_NAME,
|
||||
&altera_fpga2sdram_br_ops, priv);
|
||||
if (!br)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, br);
|
||||
|
||||
ret = fpga_bridge_register(br);
|
||||
if (ret) {
|
||||
fpga_bridge_free(br);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "driver initialized with handoff %08x\n", priv->mask);
|
||||
|
||||
|
|
|
@ -213,7 +213,6 @@ static int altera_freeze_br_probe(struct platform_device *pdev)
|
|||
struct fpga_bridge *br;
|
||||
struct resource *res;
|
||||
u32 status, revision;
|
||||
int ret;
|
||||
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
@ -245,20 +244,14 @@ static int altera_freeze_br_probe(struct platform_device *pdev)
|
|||
|
||||
priv->base_addr = base_addr;
|
||||
|
||||
br = fpga_bridge_create(dev, FREEZE_BRIDGE_NAME,
|
||||
&altera_freeze_br_br_ops, priv);
|
||||
br = devm_fpga_bridge_create(dev, FREEZE_BRIDGE_NAME,
|
||||
&altera_freeze_br_br_ops, priv);
|
||||
if (!br)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, br);
|
||||
|
||||
ret = fpga_bridge_register(br);
|
||||
if (ret) {
|
||||
fpga_bridge_free(br);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return fpga_bridge_register(br);
|
||||
}
|
||||
|
||||
static int altera_freeze_br_remove(struct platform_device *pdev)
|
||||
|
|
|
@ -180,7 +180,8 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev)
|
|||
}
|
||||
}
|
||||
|
||||
br = fpga_bridge_create(dev, priv->name, &altera_hps2fpga_br_ops, priv);
|
||||
br = devm_fpga_bridge_create(dev, priv->name,
|
||||
&altera_hps2fpga_br_ops, priv);
|
||||
if (!br) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
|
@ -190,12 +191,10 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev)
|
|||
|
||||
ret = fpga_bridge_register(br);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
fpga_bridge_free(br);
|
||||
err:
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
|
|
|
@ -177,7 +177,6 @@ int alt_pr_register(struct device *dev, void __iomem *reg_base)
|
|||
{
|
||||
struct alt_pr_priv *priv;
|
||||
struct fpga_manager *mgr;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
|
@ -192,17 +191,13 @@ int alt_pr_register(struct device *dev, void __iomem *reg_base)
|
|||
(val & ALT_PR_CSR_STATUS_MSK) >> ALT_PR_CSR_STATUS_SFT,
|
||||
(int)(val & ALT_PR_CSR_PR_START));
|
||||
|
||||
mgr = fpga_mgr_create(dev, dev_name(dev), &alt_pr_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, dev_name(dev), &alt_pr_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(alt_pr_register);
|
||||
|
||||
|
|
|
@ -239,7 +239,6 @@ static int altera_ps_probe(struct spi_device *spi)
|
|||
struct altera_ps_conf *conf;
|
||||
const struct of_device_id *of_id;
|
||||
struct fpga_manager *mgr;
|
||||
int ret;
|
||||
|
||||
conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL);
|
||||
if (!conf)
|
||||
|
@ -275,18 +274,14 @@ static int altera_ps_probe(struct spi_device *spi)
|
|||
snprintf(conf->mgr_name, sizeof(conf->mgr_name), "%s %s",
|
||||
dev_driver_string(&spi->dev), dev_name(&spi->dev));
|
||||
|
||||
mgr = fpga_mgr_create(&spi->dev, conf->mgr_name,
|
||||
&altera_ps_ops, conf);
|
||||
mgr = devm_fpga_mgr_create(&spi->dev, conf->mgr_name,
|
||||
&altera_ps_ops, conf);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_set_drvdata(spi, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int altera_ps_remove(struct spi_device *spi)
|
||||
|
|
|
@ -70,7 +70,7 @@ static int afu_dma_adjust_locked_vm(struct device *dev, long npages, bool incr)
|
|||
dev_dbg(dev, "[%d] RLIMIT_MEMLOCK %c%ld %ld/%ld%s\n", current->pid,
|
||||
incr ? '+' : '-', npages << PAGE_SHIFT,
|
||||
current->mm->locked_vm << PAGE_SHIFT, rlimit(RLIMIT_MEMLOCK),
|
||||
ret ? "- execeeded" : "");
|
||||
ret ? "- exceeded" : "");
|
||||
|
||||
up_write(¤t->mm->mmap_sem);
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ static int fme_br_probe(struct platform_device *pdev)
|
|||
struct device *dev = &pdev->dev;
|
||||
struct fme_br_priv *priv;
|
||||
struct fpga_bridge *br;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
|
@ -69,18 +68,14 @@ static int fme_br_probe(struct platform_device *pdev)
|
|||
|
||||
priv->pdata = dev_get_platdata(dev);
|
||||
|
||||
br = fpga_bridge_create(dev, "DFL FPGA FME Bridge",
|
||||
&fme_bridge_ops, priv);
|
||||
br = devm_fpga_bridge_create(dev, "DFL FPGA FME Bridge",
|
||||
&fme_bridge_ops, priv);
|
||||
if (!br)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, br);
|
||||
|
||||
ret = fpga_bridge_register(br);
|
||||
if (ret)
|
||||
fpga_bridge_free(br);
|
||||
|
||||
return ret;
|
||||
return fpga_bridge_register(br);
|
||||
}
|
||||
|
||||
static int fme_br_remove(struct platform_device *pdev)
|
||||
|
|
|
@ -201,7 +201,7 @@ static int fme_mgr_write(struct fpga_manager *mgr,
|
|||
}
|
||||
|
||||
if (count < 4) {
|
||||
dev_err(dev, "Invaild PR bitstream size\n");
|
||||
dev_err(dev, "Invalid PR bitstream size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,6 @@ static int fme_mgr_probe(struct platform_device *pdev)
|
|||
struct fme_mgr_priv *priv;
|
||||
struct fpga_manager *mgr;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
|
@ -309,19 +308,15 @@ static int fme_mgr_probe(struct platform_device *pdev)
|
|||
|
||||
fme_mgr_get_compat_id(priv->ioaddr, compat_id);
|
||||
|
||||
mgr = fpga_mgr_create(dev, "DFL FME FPGA Manager",
|
||||
&fme_mgr_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, "DFL FME FPGA Manager",
|
||||
&fme_mgr_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
mgr->compat_id = compat_id;
|
||||
platform_set_drvdata(pdev, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int fme_mgr_remove(struct platform_device *pdev)
|
||||
|
|
|
@ -39,7 +39,7 @@ static int fme_region_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(mgr))
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
region = fpga_region_create(dev, mgr, fme_region_get_bridges);
|
||||
region = devm_fpga_region_create(dev, mgr, fme_region_get_bridges);
|
||||
if (!region) {
|
||||
ret = -ENOMEM;
|
||||
goto eprobe_mgr_put;
|
||||
|
@ -51,14 +51,12 @@ static int fme_region_probe(struct platform_device *pdev)
|
|||
|
||||
ret = fpga_region_register(region);
|
||||
if (ret)
|
||||
goto region_free;
|
||||
goto eprobe_mgr_put;
|
||||
|
||||
dev_dbg(dev, "DFL FME FPGA Region probed\n");
|
||||
|
||||
return 0;
|
||||
|
||||
region_free:
|
||||
fpga_region_free(region);
|
||||
eprobe_mgr_put:
|
||||
fpga_mgr_put(mgr);
|
||||
return ret;
|
||||
|
|
|
@ -899,7 +899,7 @@ dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info)
|
|||
if (!cdev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
cdev->region = fpga_region_create(info->dev, NULL, NULL);
|
||||
cdev->region = devm_fpga_region_create(info->dev, NULL, NULL);
|
||||
if (!cdev->region) {
|
||||
ret = -ENOMEM;
|
||||
goto free_cdev_exit;
|
||||
|
@ -911,7 +911,7 @@ dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info)
|
|||
|
||||
ret = fpga_region_register(cdev->region);
|
||||
if (ret)
|
||||
goto free_region_exit;
|
||||
goto free_cdev_exit;
|
||||
|
||||
/* create and init build info for enumeration */
|
||||
binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL);
|
||||
|
@ -942,8 +942,6 @@ dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info)
|
|||
|
||||
unregister_region_exit:
|
||||
fpga_region_unregister(cdev->region);
|
||||
free_region_exit:
|
||||
fpga_region_free(cdev->region);
|
||||
free_cdev_exit:
|
||||
devm_kfree(info->dev, cdev);
|
||||
return ERR_PTR(ret);
|
||||
|
|
|
@ -324,6 +324,9 @@ ATTRIBUTE_GROUPS(fpga_bridge);
|
|||
* @br_ops: pointer to structure of fpga bridge ops
|
||||
* @priv: FPGA bridge private data
|
||||
*
|
||||
* The caller of this function is responsible for freeing the bridge with
|
||||
* fpga_bridge_free(). Using devm_fpga_bridge_create() instead is recommended.
|
||||
*
|
||||
* Return: struct fpga_bridge or NULL
|
||||
*/
|
||||
struct fpga_bridge *fpga_bridge_create(struct device *dev, const char *name,
|
||||
|
@ -378,8 +381,8 @@ error_kfree:
|
|||
EXPORT_SYMBOL_GPL(fpga_bridge_create);
|
||||
|
||||
/**
|
||||
* fpga_bridge_free - free a fpga bridge and its id
|
||||
* @bridge: FPGA bridge struct created by fpga_bridge_create
|
||||
* fpga_bridge_free - free a fpga bridge created by fpga_bridge_create()
|
||||
* @bridge: FPGA bridge struct
|
||||
*/
|
||||
void fpga_bridge_free(struct fpga_bridge *bridge)
|
||||
{
|
||||
|
@ -388,9 +391,56 @@ void fpga_bridge_free(struct fpga_bridge *bridge)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(fpga_bridge_free);
|
||||
|
||||
static void devm_fpga_bridge_release(struct device *dev, void *res)
|
||||
{
|
||||
struct fpga_bridge *bridge = *(struct fpga_bridge **)res;
|
||||
|
||||
fpga_bridge_free(bridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* fpga_bridge_register - register a fpga bridge
|
||||
* @bridge: FPGA bridge struct created by fpga_bridge_create
|
||||
* devm_fpga_bridge_create - create and init a managed struct fpga_bridge
|
||||
* @dev: FPGA bridge device from pdev
|
||||
* @name: FPGA bridge name
|
||||
* @br_ops: pointer to structure of fpga bridge ops
|
||||
* @priv: FPGA bridge private data
|
||||
*
|
||||
* This function is intended for use in a FPGA bridge driver's probe function.
|
||||
* After the bridge driver creates the struct with devm_fpga_bridge_create(), it
|
||||
* should register the bridge with fpga_bridge_register(). The bridge driver's
|
||||
* remove function should call fpga_bridge_unregister(). The bridge struct
|
||||
* allocated with this function will be freed automatically on driver detach.
|
||||
* This includes the case of a probe function returning error before calling
|
||||
* fpga_bridge_register(), the struct will still get cleaned up.
|
||||
*
|
||||
* Return: struct fpga_bridge or NULL
|
||||
*/
|
||||
struct fpga_bridge
|
||||
*devm_fpga_bridge_create(struct device *dev, const char *name,
|
||||
const struct fpga_bridge_ops *br_ops, void *priv)
|
||||
{
|
||||
struct fpga_bridge **ptr, *bridge;
|
||||
|
||||
ptr = devres_alloc(devm_fpga_bridge_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
bridge = fpga_bridge_create(dev, name, br_ops, priv);
|
||||
if (!bridge) {
|
||||
devres_free(ptr);
|
||||
} else {
|
||||
*ptr = bridge;
|
||||
devres_add(dev, ptr);
|
||||
}
|
||||
|
||||
return bridge;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_fpga_bridge_create);
|
||||
|
||||
/**
|
||||
* fpga_bridge_register - register a FPGA bridge
|
||||
*
|
||||
* @bridge: FPGA bridge struct
|
||||
*
|
||||
* Return: 0 for success, error code otherwise.
|
||||
*/
|
||||
|
@ -412,8 +462,11 @@ int fpga_bridge_register(struct fpga_bridge *bridge)
|
|||
EXPORT_SYMBOL_GPL(fpga_bridge_register);
|
||||
|
||||
/**
|
||||
* fpga_bridge_unregister - unregister and free a fpga bridge
|
||||
* @bridge: FPGA bridge struct created by fpga_bridge_create
|
||||
* fpga_bridge_unregister - unregister a FPGA bridge
|
||||
*
|
||||
* @bridge: FPGA bridge struct
|
||||
*
|
||||
* This function is intended for use in a FPGA bridge driver's remove function.
|
||||
*/
|
||||
void fpga_bridge_unregister(struct fpga_bridge *bridge)
|
||||
{
|
||||
|
@ -430,9 +483,6 @@ EXPORT_SYMBOL_GPL(fpga_bridge_unregister);
|
|||
|
||||
static void fpga_bridge_dev_release(struct device *dev)
|
||||
{
|
||||
struct fpga_bridge *bridge = to_fpga_bridge(dev);
|
||||
|
||||
fpga_bridge_free(bridge);
|
||||
}
|
||||
|
||||
static int __init fpga_bridge_dev_init(void)
|
||||
|
|
|
@ -558,6 +558,9 @@ EXPORT_SYMBOL_GPL(fpga_mgr_unlock);
|
|||
* @mops: pointer to structure of fpga manager ops
|
||||
* @priv: fpga manager private data
|
||||
*
|
||||
* The caller of this function is responsible for freeing the struct with
|
||||
* fpga_mgr_free(). Using devm_fpga_mgr_create() instead is recommended.
|
||||
*
|
||||
* Return: pointer to struct fpga_manager or NULL
|
||||
*/
|
||||
struct fpga_manager *fpga_mgr_create(struct device *dev, const char *name,
|
||||
|
@ -618,8 +621,8 @@ error_kfree:
|
|||
EXPORT_SYMBOL_GPL(fpga_mgr_create);
|
||||
|
||||
/**
|
||||
* fpga_mgr_free - deallocate a FPGA manager
|
||||
* @mgr: fpga manager struct created by fpga_mgr_create
|
||||
* fpga_mgr_free - free a FPGA manager created with fpga_mgr_create()
|
||||
* @mgr: fpga manager struct
|
||||
*/
|
||||
void fpga_mgr_free(struct fpga_manager *mgr)
|
||||
{
|
||||
|
@ -628,9 +631,55 @@ void fpga_mgr_free(struct fpga_manager *mgr)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(fpga_mgr_free);
|
||||
|
||||
static void devm_fpga_mgr_release(struct device *dev, void *res)
|
||||
{
|
||||
struct fpga_manager *mgr = *(struct fpga_manager **)res;
|
||||
|
||||
fpga_mgr_free(mgr);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_fpga_mgr_create - create and initialize a managed FPGA manager struct
|
||||
* @dev: fpga manager device from pdev
|
||||
* @name: fpga manager name
|
||||
* @mops: pointer to structure of fpga manager ops
|
||||
* @priv: fpga manager private data
|
||||
*
|
||||
* This function is intended for use in a FPGA manager driver's probe function.
|
||||
* After the manager driver creates the manager struct with
|
||||
* devm_fpga_mgr_create(), it should register it with fpga_mgr_register(). The
|
||||
* manager driver's remove function should call fpga_mgr_unregister(). The
|
||||
* manager struct allocated with this function will be freed automatically on
|
||||
* driver detach. This includes the case of a probe function returning error
|
||||
* before calling fpga_mgr_register(), the struct will still get cleaned up.
|
||||
*
|
||||
* Return: pointer to struct fpga_manager or NULL
|
||||
*/
|
||||
struct fpga_manager *devm_fpga_mgr_create(struct device *dev, const char *name,
|
||||
const struct fpga_manager_ops *mops,
|
||||
void *priv)
|
||||
{
|
||||
struct fpga_manager **ptr, *mgr;
|
||||
|
||||
ptr = devres_alloc(devm_fpga_mgr_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
mgr = fpga_mgr_create(dev, name, mops, priv);
|
||||
if (!mgr) {
|
||||
devres_free(ptr);
|
||||
} else {
|
||||
*ptr = mgr;
|
||||
devres_add(dev, ptr);
|
||||
}
|
||||
|
||||
return mgr;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_fpga_mgr_create);
|
||||
|
||||
/**
|
||||
* fpga_mgr_register - register a FPGA manager
|
||||
* @mgr: fpga manager struct created by fpga_mgr_create
|
||||
* @mgr: fpga manager struct
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
|
@ -661,8 +710,10 @@ error_device:
|
|||
EXPORT_SYMBOL_GPL(fpga_mgr_register);
|
||||
|
||||
/**
|
||||
* fpga_mgr_unregister - unregister and free a FPGA manager
|
||||
* @mgr: fpga manager struct
|
||||
* fpga_mgr_unregister - unregister a FPGA manager
|
||||
* @mgr: fpga manager struct
|
||||
*
|
||||
* This function is intended for use in a FPGA manager driver's remove function.
|
||||
*/
|
||||
void fpga_mgr_unregister(struct fpga_manager *mgr)
|
||||
{
|
||||
|
@ -681,9 +732,6 @@ EXPORT_SYMBOL_GPL(fpga_mgr_unregister);
|
|||
|
||||
static void fpga_mgr_dev_release(struct device *dev)
|
||||
{
|
||||
struct fpga_manager *mgr = to_fpga_manager(dev);
|
||||
|
||||
fpga_mgr_free(mgr);
|
||||
}
|
||||
|
||||
static int __init fpga_mgr_class_init(void)
|
||||
|
|
|
@ -185,6 +185,10 @@ ATTRIBUTE_GROUPS(fpga_region);
|
|||
* @mgr: manager that programs this region
|
||||
* @get_bridges: optional function to get bridges to a list
|
||||
*
|
||||
* The caller of this function is responsible for freeing the resulting region
|
||||
* struct with fpga_region_free(). Using devm_fpga_region_create() instead is
|
||||
* recommended.
|
||||
*
|
||||
* Return: struct fpga_region or NULL
|
||||
*/
|
||||
struct fpga_region
|
||||
|
@ -230,8 +234,8 @@ err_free:
|
|||
EXPORT_SYMBOL_GPL(fpga_region_create);
|
||||
|
||||
/**
|
||||
* fpga_region_free - free a struct fpga_region
|
||||
* @region: FPGA region created by fpga_region_create
|
||||
* fpga_region_free - free a FPGA region created by fpga_region_create()
|
||||
* @region: FPGA region
|
||||
*/
|
||||
void fpga_region_free(struct fpga_region *region)
|
||||
{
|
||||
|
@ -240,21 +244,69 @@ void fpga_region_free(struct fpga_region *region)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(fpga_region_free);
|
||||
|
||||
static void devm_fpga_region_release(struct device *dev, void *res)
|
||||
{
|
||||
struct fpga_region *region = *(struct fpga_region **)res;
|
||||
|
||||
fpga_region_free(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_fpga_region_create - create and initialize a managed FPGA region struct
|
||||
* @dev: device parent
|
||||
* @mgr: manager that programs this region
|
||||
* @get_bridges: optional function to get bridges to a list
|
||||
*
|
||||
* This function is intended for use in a FPGA region driver's probe function.
|
||||
* After the region driver creates the region struct with
|
||||
* devm_fpga_region_create(), it should register it with fpga_region_register().
|
||||
* The region driver's remove function should call fpga_region_unregister().
|
||||
* The region struct allocated with this function will be freed automatically on
|
||||
* driver detach. This includes the case of a probe function returning error
|
||||
* before calling fpga_region_register(), the struct will still get cleaned up.
|
||||
*
|
||||
* Return: struct fpga_region or NULL
|
||||
*/
|
||||
struct fpga_region
|
||||
*devm_fpga_region_create(struct device *dev,
|
||||
struct fpga_manager *mgr,
|
||||
int (*get_bridges)(struct fpga_region *))
|
||||
{
|
||||
struct fpga_region **ptr, *region;
|
||||
|
||||
ptr = devres_alloc(devm_fpga_region_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
region = fpga_region_create(dev, mgr, get_bridges);
|
||||
if (!region) {
|
||||
devres_free(ptr);
|
||||
} else {
|
||||
*ptr = region;
|
||||
devres_add(dev, ptr);
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_fpga_region_create);
|
||||
|
||||
/**
|
||||
* fpga_region_register - register a FPGA region
|
||||
* @region: FPGA region created by fpga_region_create
|
||||
* @region: FPGA region
|
||||
*
|
||||
* Return: 0 or -errno
|
||||
*/
|
||||
int fpga_region_register(struct fpga_region *region)
|
||||
{
|
||||
return device_add(®ion->dev);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fpga_region_register);
|
||||
|
||||
/**
|
||||
* fpga_region_unregister - unregister and free a FPGA region
|
||||
* fpga_region_unregister - unregister a FPGA region
|
||||
* @region: FPGA region
|
||||
*
|
||||
* This function is intended for use in a FPGA region driver's remove function.
|
||||
*/
|
||||
void fpga_region_unregister(struct fpga_region *region)
|
||||
{
|
||||
|
@ -264,9 +316,6 @@ EXPORT_SYMBOL_GPL(fpga_region_unregister);
|
|||
|
||||
static void fpga_region_dev_release(struct device *dev)
|
||||
{
|
||||
struct fpga_region *region = to_fpga_region(dev);
|
||||
|
||||
fpga_region_free(region);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -175,18 +175,14 @@ static int ice40_fpga_probe(struct spi_device *spi)
|
|||
return ret;
|
||||
}
|
||||
|
||||
mgr = fpga_mgr_create(dev, "Lattice iCE40 FPGA Manager",
|
||||
&ice40_fpga_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, "Lattice iCE40 FPGA Manager",
|
||||
&ice40_fpga_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_set_drvdata(spi, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int ice40_fpga_remove(struct spi_device *spi)
|
||||
|
|
|
@ -356,25 +356,20 @@ static int machxo2_spi_probe(struct spi_device *spi)
|
|||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct fpga_manager *mgr;
|
||||
int ret;
|
||||
|
||||
if (spi->max_speed_hz > MACHXO2_MAX_SPEED) {
|
||||
dev_err(dev, "Speed is too high\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mgr = fpga_mgr_create(dev, "Lattice MachXO2 SPI FPGA Manager",
|
||||
&machxo2_ops, spi);
|
||||
mgr = devm_fpga_mgr_create(dev, "Lattice MachXO2 SPI FPGA Manager",
|
||||
&machxo2_ops, spi);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_set_drvdata(spi, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int machxo2_spi_remove(struct spi_device *spi)
|
||||
|
|
|
@ -410,7 +410,7 @@ static int of_fpga_region_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(mgr))
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
region = fpga_region_create(dev, mgr, of_fpga_region_get_bridges);
|
||||
region = devm_fpga_region_create(dev, mgr, of_fpga_region_get_bridges);
|
||||
if (!region) {
|
||||
ret = -ENOMEM;
|
||||
goto eprobe_mgr_put;
|
||||
|
@ -418,7 +418,7 @@ static int of_fpga_region_probe(struct platform_device *pdev)
|
|||
|
||||
ret = fpga_region_register(region);
|
||||
if (ret)
|
||||
goto eprobe_free;
|
||||
goto eprobe_mgr_put;
|
||||
|
||||
of_platform_populate(np, fpga_region_of_match, NULL, ®ion->dev);
|
||||
dev_set_drvdata(dev, region);
|
||||
|
@ -427,8 +427,6 @@ static int of_fpga_region_probe(struct platform_device *pdev)
|
|||
|
||||
return 0;
|
||||
|
||||
eprobe_free:
|
||||
fpga_region_free(region);
|
||||
eprobe_mgr_put:
|
||||
fpga_mgr_put(mgr);
|
||||
return ret;
|
||||
|
|
|
@ -508,8 +508,8 @@ static int socfpga_a10_fpga_probe(struct platform_device *pdev)
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
mgr = fpga_mgr_create(dev, "SoCFPGA Arria10 FPGA Manager",
|
||||
&socfpga_a10_fpga_mgr_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, "SoCFPGA Arria10 FPGA Manager",
|
||||
&socfpga_a10_fpga_mgr_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -517,7 +517,6 @@ static int socfpga_a10_fpga_probe(struct platform_device *pdev)
|
|||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret) {
|
||||
fpga_mgr_free(mgr);
|
||||
clk_disable_unprepare(priv->clk);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -571,18 +571,14 @@ static int socfpga_fpga_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
mgr = fpga_mgr_create(dev, "Altera SOCFPGA FPGA Manager",
|
||||
&socfpga_fpga_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, "Altera SOCFPGA FPGA Manager",
|
||||
&socfpga_fpga_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int socfpga_fpga_remove(struct platform_device *pdev)
|
||||
|
|
|
@ -118,7 +118,6 @@ static int ts73xx_fpga_probe(struct platform_device *pdev)
|
|||
struct ts73xx_fpga_priv *priv;
|
||||
struct fpga_manager *mgr;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
|
@ -133,18 +132,14 @@ static int ts73xx_fpga_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(priv->io_base);
|
||||
}
|
||||
|
||||
mgr = fpga_mgr_create(kdev, "TS-73xx FPGA Manager",
|
||||
&ts73xx_fpga_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(kdev, "TS-73xx FPGA Manager",
|
||||
&ts73xx_fpga_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int ts73xx_fpga_remove(struct platform_device *pdev)
|
||||
|
|
|
@ -121,8 +121,8 @@ static int xlnx_pr_decoupler_probe(struct platform_device *pdev)
|
|||
|
||||
clk_disable(priv->clk);
|
||||
|
||||
br = fpga_bridge_create(&pdev->dev, "Xilinx PR Decoupler",
|
||||
&xlnx_pr_decoupler_br_ops, priv);
|
||||
br = devm_fpga_bridge_create(&pdev->dev, "Xilinx PR Decoupler",
|
||||
&xlnx_pr_decoupler_br_ops, priv);
|
||||
if (!br) {
|
||||
err = -ENOMEM;
|
||||
goto err_clk;
|
||||
|
|
|
@ -144,7 +144,6 @@ static int xilinx_spi_probe(struct spi_device *spi)
|
|||
{
|
||||
struct xilinx_spi_conf *conf;
|
||||
struct fpga_manager *mgr;
|
||||
int ret;
|
||||
|
||||
conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL);
|
||||
if (!conf)
|
||||
|
@ -167,18 +166,15 @@ static int xilinx_spi_probe(struct spi_device *spi)
|
|||
return PTR_ERR(conf->done);
|
||||
}
|
||||
|
||||
mgr = fpga_mgr_create(&spi->dev, "Xilinx Slave Serial FPGA Manager",
|
||||
&xilinx_spi_ops, conf);
|
||||
mgr = devm_fpga_mgr_create(&spi->dev,
|
||||
"Xilinx Slave Serial FPGA Manager",
|
||||
&xilinx_spi_ops, conf);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_set_drvdata(spi, mgr);
|
||||
|
||||
ret = fpga_mgr_register(mgr);
|
||||
if (ret)
|
||||
fpga_mgr_free(mgr);
|
||||
|
||||
return ret;
|
||||
return fpga_mgr_register(mgr);
|
||||
}
|
||||
|
||||
static int xilinx_spi_remove(struct spi_device *spi)
|
||||
|
|
|
@ -614,8 +614,8 @@ static int zynq_fpga_probe(struct platform_device *pdev)
|
|||
|
||||
clk_disable(priv->clk);
|
||||
|
||||
mgr = fpga_mgr_create(dev, "Xilinx Zynq FPGA Manager",
|
||||
&zynq_fpga_ops, priv);
|
||||
mgr = devm_fpga_mgr_create(dev, "Xilinx Zynq FPGA Manager",
|
||||
&zynq_fpga_ops, priv);
|
||||
if (!mgr)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -624,7 +624,6 @@ static int zynq_fpga_probe(struct platform_device *pdev)
|
|||
err = fpga_mgr_register(mgr);
|
||||
if (err) {
|
||||
dev_err(dev, "unable to register FPGA manager\n");
|
||||
fpga_mgr_free(mgr);
|
||||
clk_unprepare(priv->clk);
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -79,85 +79,96 @@ void vmbus_setevent(struct vmbus_channel *channel)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_setevent);
|
||||
|
||||
/*
|
||||
* vmbus_open - Open the specified channel.
|
||||
*/
|
||||
int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
||||
u32 recv_ringbuffer_size, void *userdata, u32 userdatalen,
|
||||
void (*onchannelcallback)(void *context), void *context)
|
||||
/* vmbus_free_ring - drop mapping of ring buffer */
|
||||
void vmbus_free_ring(struct vmbus_channel *channel)
|
||||
{
|
||||
hv_ringbuffer_cleanup(&channel->outbound);
|
||||
hv_ringbuffer_cleanup(&channel->inbound);
|
||||
|
||||
if (channel->ringbuffer_page) {
|
||||
__free_pages(channel->ringbuffer_page,
|
||||
get_order(channel->ringbuffer_pagecount
|
||||
<< PAGE_SHIFT));
|
||||
channel->ringbuffer_page = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_free_ring);
|
||||
|
||||
/* vmbus_alloc_ring - allocate and map pages for ring buffer */
|
||||
int vmbus_alloc_ring(struct vmbus_channel *newchannel,
|
||||
u32 send_size, u32 recv_size)
|
||||
{
|
||||
struct page *page;
|
||||
int order;
|
||||
|
||||
if (send_size % PAGE_SIZE || recv_size % PAGE_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
/* Allocate the ring buffer */
|
||||
order = get_order(send_size + recv_size);
|
||||
page = alloc_pages_node(cpu_to_node(newchannel->target_cpu),
|
||||
GFP_KERNEL|__GFP_ZERO, order);
|
||||
|
||||
if (!page)
|
||||
page = alloc_pages(GFP_KERNEL|__GFP_ZERO, order);
|
||||
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
|
||||
newchannel->ringbuffer_page = page;
|
||||
newchannel->ringbuffer_pagecount = (send_size + recv_size) >> PAGE_SHIFT;
|
||||
newchannel->ringbuffer_send_offset = send_size >> PAGE_SHIFT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_alloc_ring);
|
||||
|
||||
static int __vmbus_open(struct vmbus_channel *newchannel,
|
||||
void *userdata, u32 userdatalen,
|
||||
void (*onchannelcallback)(void *context), void *context)
|
||||
{
|
||||
struct vmbus_channel_open_channel *open_msg;
|
||||
struct vmbus_channel_msginfo *open_info = NULL;
|
||||
struct page *page = newchannel->ringbuffer_page;
|
||||
u32 send_pages, recv_pages;
|
||||
unsigned long flags;
|
||||
int ret, err = 0;
|
||||
struct page *page;
|
||||
int err;
|
||||
|
||||
if (send_ringbuffer_size % PAGE_SIZE ||
|
||||
recv_ringbuffer_size % PAGE_SIZE)
|
||||
if (userdatalen > MAX_USER_DEFINED_BYTES)
|
||||
return -EINVAL;
|
||||
|
||||
send_pages = newchannel->ringbuffer_send_offset;
|
||||
recv_pages = newchannel->ringbuffer_pagecount - send_pages;
|
||||
|
||||
spin_lock_irqsave(&newchannel->lock, flags);
|
||||
if (newchannel->state == CHANNEL_OPEN_STATE) {
|
||||
newchannel->state = CHANNEL_OPENING_STATE;
|
||||
} else {
|
||||
if (newchannel->state != CHANNEL_OPEN_STATE) {
|
||||
spin_unlock_irqrestore(&newchannel->lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
spin_unlock_irqrestore(&newchannel->lock, flags);
|
||||
|
||||
newchannel->state = CHANNEL_OPENING_STATE;
|
||||
newchannel->onchannel_callback = onchannelcallback;
|
||||
newchannel->channel_callback_context = context;
|
||||
|
||||
/* Allocate the ring buffer */
|
||||
page = alloc_pages_node(cpu_to_node(newchannel->target_cpu),
|
||||
GFP_KERNEL|__GFP_ZERO,
|
||||
get_order(send_ringbuffer_size +
|
||||
recv_ringbuffer_size));
|
||||
|
||||
if (!page)
|
||||
page = alloc_pages(GFP_KERNEL|__GFP_ZERO,
|
||||
get_order(send_ringbuffer_size +
|
||||
recv_ringbuffer_size));
|
||||
|
||||
if (!page) {
|
||||
err = -ENOMEM;
|
||||
goto error_set_chnstate;
|
||||
}
|
||||
|
||||
newchannel->ringbuffer_pages = page_address(page);
|
||||
newchannel->ringbuffer_pagecount = (send_ringbuffer_size +
|
||||
recv_ringbuffer_size) >> PAGE_SHIFT;
|
||||
|
||||
ret = hv_ringbuffer_init(&newchannel->outbound, page,
|
||||
send_ringbuffer_size >> PAGE_SHIFT);
|
||||
|
||||
if (ret != 0) {
|
||||
err = ret;
|
||||
goto error_free_pages;
|
||||
}
|
||||
|
||||
ret = hv_ringbuffer_init(&newchannel->inbound,
|
||||
&page[send_ringbuffer_size >> PAGE_SHIFT],
|
||||
recv_ringbuffer_size >> PAGE_SHIFT);
|
||||
if (ret != 0) {
|
||||
err = ret;
|
||||
goto error_free_pages;
|
||||
}
|
||||
err = hv_ringbuffer_init(&newchannel->outbound, page, send_pages);
|
||||
if (err)
|
||||
goto error_clean_ring;
|
||||
|
||||
err = hv_ringbuffer_init(&newchannel->inbound,
|
||||
&page[send_pages], recv_pages);
|
||||
if (err)
|
||||
goto error_clean_ring;
|
||||
|
||||
/* Establish the gpadl for the ring buffer */
|
||||
newchannel->ringbuffer_gpadlhandle = 0;
|
||||
|
||||
ret = vmbus_establish_gpadl(newchannel,
|
||||
page_address(page),
|
||||
send_ringbuffer_size +
|
||||
recv_ringbuffer_size,
|
||||
err = vmbus_establish_gpadl(newchannel,
|
||||
page_address(newchannel->ringbuffer_page),
|
||||
(send_pages + recv_pages) << PAGE_SHIFT,
|
||||
&newchannel->ringbuffer_gpadlhandle);
|
||||
|
||||
if (ret != 0) {
|
||||
err = ret;
|
||||
goto error_free_pages;
|
||||
}
|
||||
if (err)
|
||||
goto error_clean_ring;
|
||||
|
||||
/* Create and init the channel open message */
|
||||
open_info = kmalloc(sizeof(*open_info) +
|
||||
|
@ -176,15 +187,9 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
|||
open_msg->openid = newchannel->offermsg.child_relid;
|
||||
open_msg->child_relid = newchannel->offermsg.child_relid;
|
||||
open_msg->ringbuffer_gpadlhandle = newchannel->ringbuffer_gpadlhandle;
|
||||
open_msg->downstream_ringbuffer_pageoffset = send_ringbuffer_size >>
|
||||
PAGE_SHIFT;
|
||||
open_msg->downstream_ringbuffer_pageoffset = newchannel->ringbuffer_send_offset;
|
||||
open_msg->target_vp = newchannel->target_vp;
|
||||
|
||||
if (userdatalen > MAX_USER_DEFINED_BYTES) {
|
||||
err = -EINVAL;
|
||||
goto error_free_gpadl;
|
||||
}
|
||||
|
||||
if (userdatalen)
|
||||
memcpy(open_msg->userdata, userdata, userdatalen);
|
||||
|
||||
|
@ -195,18 +200,16 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
|||
|
||||
if (newchannel->rescind) {
|
||||
err = -ENODEV;
|
||||
goto error_free_gpadl;
|
||||
goto error_free_info;
|
||||
}
|
||||
|
||||
ret = vmbus_post_msg(open_msg,
|
||||
err = vmbus_post_msg(open_msg,
|
||||
sizeof(struct vmbus_channel_open_channel), true);
|
||||
|
||||
trace_vmbus_open(open_msg, ret);
|
||||
trace_vmbus_open(open_msg, err);
|
||||
|
||||
if (ret != 0) {
|
||||
err = ret;
|
||||
if (err != 0)
|
||||
goto error_clean_msglist;
|
||||
}
|
||||
|
||||
wait_for_completion(&open_info->waitevent);
|
||||
|
||||
|
@ -216,12 +219,12 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
|||
|
||||
if (newchannel->rescind) {
|
||||
err = -ENODEV;
|
||||
goto error_free_gpadl;
|
||||
goto error_free_info;
|
||||
}
|
||||
|
||||
if (open_info->response.open_result.status) {
|
||||
err = -EAGAIN;
|
||||
goto error_free_gpadl;
|
||||
goto error_free_info;
|
||||
}
|
||||
|
||||
newchannel->state = CHANNEL_OPENED_STATE;
|
||||
|
@ -232,19 +235,50 @@ error_clean_msglist:
|
|||
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
|
||||
list_del(&open_info->msglistentry);
|
||||
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||
|
||||
error_free_info:
|
||||
kfree(open_info);
|
||||
error_free_gpadl:
|
||||
vmbus_teardown_gpadl(newchannel, newchannel->ringbuffer_gpadlhandle);
|
||||
kfree(open_info);
|
||||
error_free_pages:
|
||||
newchannel->ringbuffer_gpadlhandle = 0;
|
||||
error_clean_ring:
|
||||
hv_ringbuffer_cleanup(&newchannel->outbound);
|
||||
hv_ringbuffer_cleanup(&newchannel->inbound);
|
||||
__free_pages(page,
|
||||
get_order(send_ringbuffer_size + recv_ringbuffer_size));
|
||||
error_set_chnstate:
|
||||
newchannel->state = CHANNEL_OPEN_STATE;
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* vmbus_connect_ring - Open the channel but reuse ring buffer
|
||||
*/
|
||||
int vmbus_connect_ring(struct vmbus_channel *newchannel,
|
||||
void (*onchannelcallback)(void *context), void *context)
|
||||
{
|
||||
return __vmbus_open(newchannel, NULL, 0, onchannelcallback, context);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_connect_ring);
|
||||
|
||||
/*
|
||||
* vmbus_open - Open the specified channel.
|
||||
*/
|
||||
int vmbus_open(struct vmbus_channel *newchannel,
|
||||
u32 send_ringbuffer_size, u32 recv_ringbuffer_size,
|
||||
void *userdata, u32 userdatalen,
|
||||
void (*onchannelcallback)(void *context), void *context)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = vmbus_alloc_ring(newchannel, send_ringbuffer_size,
|
||||
recv_ringbuffer_size);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = __vmbus_open(newchannel, userdata, userdatalen,
|
||||
onchannelcallback, context);
|
||||
if (err)
|
||||
vmbus_free_ring(newchannel);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_open);
|
||||
|
||||
/* Used for Hyper-V Socket: a guest client's connect() to the host */
|
||||
|
@ -612,10 +646,8 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
|
|||
* in Hyper-V Manager), the driver's remove() invokes vmbus_close():
|
||||
* here we should skip most of the below cleanup work.
|
||||
*/
|
||||
if (channel->state != CHANNEL_OPENED_STATE) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (channel->state != CHANNEL_OPENED_STATE)
|
||||
return -EINVAL;
|
||||
|
||||
channel->state = CHANNEL_OPEN_STATE;
|
||||
|
||||
|
@ -637,11 +669,10 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
|
|||
* If we failed to post the close msg,
|
||||
* it is perhaps better to leak memory.
|
||||
*/
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Tear down the gpadl for the channel's ring buffer */
|
||||
if (channel->ringbuffer_gpadlhandle) {
|
||||
else if (channel->ringbuffer_gpadlhandle) {
|
||||
ret = vmbus_teardown_gpadl(channel,
|
||||
channel->ringbuffer_gpadlhandle);
|
||||
if (ret) {
|
||||
|
@ -650,74 +681,78 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
|
|||
* If we failed to teardown gpadl,
|
||||
* it is perhaps better to leak memory.
|
||||
*/
|
||||
goto out;
|
||||
}
|
||||
|
||||
channel->ringbuffer_gpadlhandle = 0;
|
||||
}
|
||||
|
||||
/* Cleanup the ring buffers for this channel */
|
||||
hv_ringbuffer_cleanup(&channel->outbound);
|
||||
hv_ringbuffer_cleanup(&channel->inbound);
|
||||
|
||||
free_pages((unsigned long)channel->ringbuffer_pages,
|
||||
get_order(channel->ringbuffer_pagecount * PAGE_SIZE));
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* disconnect ring - close all channels */
|
||||
int vmbus_disconnect_ring(struct vmbus_channel *channel)
|
||||
{
|
||||
struct vmbus_channel *cur_channel, *tmp;
|
||||
unsigned long flags;
|
||||
LIST_HEAD(list);
|
||||
int ret;
|
||||
|
||||
if (channel->primary_channel != NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Snapshot the list of subchannels */
|
||||
spin_lock_irqsave(&channel->lock, flags);
|
||||
list_splice_init(&channel->sc_list, &list);
|
||||
channel->num_sc = 0;
|
||||
spin_unlock_irqrestore(&channel->lock, flags);
|
||||
|
||||
list_for_each_entry_safe(cur_channel, tmp, &list, sc_list) {
|
||||
if (cur_channel->rescind)
|
||||
wait_for_completion(&cur_channel->rescind_event);
|
||||
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
if (vmbus_close_internal(cur_channel) == 0) {
|
||||
vmbus_free_ring(cur_channel);
|
||||
|
||||
if (cur_channel->rescind)
|
||||
hv_process_channel_removal(cur_channel);
|
||||
}
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now close the primary.
|
||||
*/
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
ret = vmbus_close_internal(channel);
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_disconnect_ring);
|
||||
|
||||
/*
|
||||
* vmbus_close - Close the specified channel
|
||||
*/
|
||||
void vmbus_close(struct vmbus_channel *channel)
|
||||
{
|
||||
struct list_head *cur, *tmp;
|
||||
struct vmbus_channel *cur_channel;
|
||||
|
||||
if (channel->primary_channel != NULL) {
|
||||
/*
|
||||
* We will only close sub-channels when
|
||||
* the primary is closed.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Close all the sub-channels first and then close the
|
||||
* primary channel.
|
||||
*/
|
||||
list_for_each_safe(cur, tmp, &channel->sc_list) {
|
||||
cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
|
||||
if (cur_channel->rescind) {
|
||||
wait_for_completion(&cur_channel->rescind_event);
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
vmbus_close_internal(cur_channel);
|
||||
hv_process_channel_removal(
|
||||
cur_channel->offermsg.child_relid);
|
||||
} else {
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
vmbus_close_internal(cur_channel);
|
||||
}
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
}
|
||||
/*
|
||||
* Now close the primary.
|
||||
*/
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
vmbus_close_internal(channel);
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
if (vmbus_disconnect_ring(channel) == 0)
|
||||
vmbus_free_ring(channel);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_close);
|
||||
|
||||
/**
|
||||
* vmbus_sendpacket() - Send the specified buffer on the given channel
|
||||
* @channel: Pointer to vmbus_channel structure.
|
||||
* @buffer: Pointer to the buffer you want to receive the data into.
|
||||
* @bufferlen: Maximum size of what the the buffer will hold
|
||||
* @channel: Pointer to vmbus_channel structure
|
||||
* @buffer: Pointer to the buffer you want to send the data from.
|
||||
* @bufferlen: Maximum size of what the buffer holds.
|
||||
* @requestid: Identifier of the request
|
||||
* @type: Type of packet that is being send e.g. negotiate, time
|
||||
* packet etc.
|
||||
* @type: Type of packet that is being sent e.g. negotiate, time
|
||||
* packet etc.
|
||||
* @flags: 0 or VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED
|
||||
*
|
||||
* Sends data in @buffer directly to hyper-v via the vmbus
|
||||
* This will send the data unparsed to hyper-v.
|
||||
* Sends data in @buffer directly to Hyper-V via the vmbus.
|
||||
* This will send the data unparsed to Hyper-V.
|
||||
*
|
||||
* Mainly used by Hyper-V drivers.
|
||||
*/
|
||||
|
@ -850,12 +885,13 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
|
|||
EXPORT_SYMBOL_GPL(vmbus_sendpacket_mpb_desc);
|
||||
|
||||
/**
|
||||
* vmbus_recvpacket() - Retrieve the user packet on the specified channel
|
||||
* @channel: Pointer to vmbus_channel structure.
|
||||
* __vmbus_recvpacket() - Retrieve the user packet on the specified channel
|
||||
* @channel: Pointer to vmbus_channel structure
|
||||
* @buffer: Pointer to the buffer you want to receive the data into.
|
||||
* @bufferlen: Maximum size of what the the buffer will hold
|
||||
* @buffer_actual_len: The actual size of the data after it was received
|
||||
* @bufferlen: Maximum size of what the buffer can hold.
|
||||
* @buffer_actual_len: The actual size of the data after it was received.
|
||||
* @requestid: Identifier of the request
|
||||
* @raw: true means keep the vmpacket_descriptor header in the received data.
|
||||
*
|
||||
* Receives directly from the hyper-v vmbus and puts the data it received
|
||||
* into Buffer. This will receive the data unparsed from hyper-v.
|
||||
|
|
|
@ -198,24 +198,19 @@ static u16 hv_get_dev_type(const struct vmbus_channel *channel)
|
|||
}
|
||||
|
||||
/**
|
||||
* vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message
|
||||
* vmbus_prep_negotiate_resp() - Create default response for Negotiate message
|
||||
* @icmsghdrp: Pointer to msg header structure
|
||||
* @icmsg_negotiate: Pointer to negotiate message structure
|
||||
* @buf: Raw buffer channel data
|
||||
* @fw_version: The framework versions we can support.
|
||||
* @fw_vercnt: The size of @fw_version.
|
||||
* @srv_version: The service versions we can support.
|
||||
* @srv_vercnt: The size of @srv_version.
|
||||
* @nego_fw_version: The selected framework version.
|
||||
* @nego_srv_version: The selected service version.
|
||||
*
|
||||
* Note: Versions are given in decreasing order.
|
||||
*
|
||||
* @icmsghdrp is of type &struct icmsg_hdr.
|
||||
* Set up and fill in default negotiate response message.
|
||||
*
|
||||
* The fw_version and fw_vercnt specifies the framework version that
|
||||
* we can support.
|
||||
*
|
||||
* The srv_version and srv_vercnt specifies the service
|
||||
* versions we can support.
|
||||
*
|
||||
* Versions are given in decreasing order.
|
||||
*
|
||||
* nego_fw_version and nego_srv_version store the selected protocol versions.
|
||||
*
|
||||
* Mainly used by Hyper-V drivers.
|
||||
*/
|
||||
bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
|
||||
|
@ -385,21 +380,14 @@ static void vmbus_release_relid(u32 relid)
|
|||
trace_vmbus_release_relid(&msg, ret);
|
||||
}
|
||||
|
||||
void hv_process_channel_removal(u32 relid)
|
||||
void hv_process_channel_removal(struct vmbus_channel *channel)
|
||||
{
|
||||
struct vmbus_channel *primary_channel;
|
||||
unsigned long flags;
|
||||
struct vmbus_channel *primary_channel, *channel;
|
||||
|
||||
BUG_ON(!mutex_is_locked(&vmbus_connection.channel_mutex));
|
||||
|
||||
/*
|
||||
* Make sure channel is valid as we may have raced.
|
||||
*/
|
||||
channel = relid2channel(relid);
|
||||
if (!channel)
|
||||
return;
|
||||
|
||||
BUG_ON(!channel->rescind);
|
||||
|
||||
if (channel->target_cpu != get_cpu()) {
|
||||
put_cpu();
|
||||
smp_call_function_single(channel->target_cpu,
|
||||
|
@ -429,7 +417,7 @@ void hv_process_channel_removal(u32 relid)
|
|||
cpumask_clear_cpu(channel->target_cpu,
|
||||
&primary_channel->alloced_cpus_in_node);
|
||||
|
||||
vmbus_release_relid(relid);
|
||||
vmbus_release_relid(channel->offermsg.child_relid);
|
||||
|
||||
free_channel(channel);
|
||||
}
|
||||
|
@ -606,16 +594,18 @@ static void init_vp_index(struct vmbus_channel *channel, u16 dev_type)
|
|||
bool perf_chn = vmbus_devs[dev_type].perf_device;
|
||||
struct vmbus_channel *primary = channel->primary_channel;
|
||||
int next_node;
|
||||
struct cpumask available_mask;
|
||||
cpumask_var_t available_mask;
|
||||
struct cpumask *alloced_mask;
|
||||
|
||||
if ((vmbus_proto_version == VERSION_WS2008) ||
|
||||
(vmbus_proto_version == VERSION_WIN7) || (!perf_chn)) {
|
||||
(vmbus_proto_version == VERSION_WIN7) || (!perf_chn) ||
|
||||
!alloc_cpumask_var(&available_mask, GFP_KERNEL)) {
|
||||
/*
|
||||
* Prior to win8, all channel interrupts are
|
||||
* delivered on cpu 0.
|
||||
* Also if the channel is not a performance critical
|
||||
* channel, bind it to cpu 0.
|
||||
* In case alloc_cpumask_var() fails, bind it to cpu 0.
|
||||
*/
|
||||
channel->numa_node = 0;
|
||||
channel->target_cpu = 0;
|
||||
|
@ -653,7 +643,7 @@ static void init_vp_index(struct vmbus_channel *channel, u16 dev_type)
|
|||
cpumask_clear(alloced_mask);
|
||||
}
|
||||
|
||||
cpumask_xor(&available_mask, alloced_mask,
|
||||
cpumask_xor(available_mask, alloced_mask,
|
||||
cpumask_of_node(primary->numa_node));
|
||||
|
||||
cur_cpu = -1;
|
||||
|
@ -671,10 +661,10 @@ static void init_vp_index(struct vmbus_channel *channel, u16 dev_type)
|
|||
}
|
||||
|
||||
while (true) {
|
||||
cur_cpu = cpumask_next(cur_cpu, &available_mask);
|
||||
cur_cpu = cpumask_next(cur_cpu, available_mask);
|
||||
if (cur_cpu >= nr_cpu_ids) {
|
||||
cur_cpu = -1;
|
||||
cpumask_copy(&available_mask,
|
||||
cpumask_copy(available_mask,
|
||||
cpumask_of_node(primary->numa_node));
|
||||
continue;
|
||||
}
|
||||
|
@ -704,6 +694,8 @@ static void init_vp_index(struct vmbus_channel *channel, u16 dev_type)
|
|||
|
||||
channel->target_cpu = cur_cpu;
|
||||
channel->target_vp = hv_cpu_number_to_vp_number(cur_cpu);
|
||||
|
||||
free_cpumask_var(available_mask);
|
||||
}
|
||||
|
||||
static void vmbus_wait_for_unload(void)
|
||||
|
@ -943,7 +935,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
|
|||
* The channel is currently not open;
|
||||
* it is safe for us to cleanup the channel.
|
||||
*/
|
||||
hv_process_channel_removal(rescind->child_relid);
|
||||
hv_process_channel_removal(channel);
|
||||
} else {
|
||||
complete(&channel->rescind_event);
|
||||
}
|
||||
|
|
|
@ -189,6 +189,17 @@ static void hv_init_clockevent_device(struct clock_event_device *dev, int cpu)
|
|||
int hv_synic_alloc(void)
|
||||
{
|
||||
int cpu;
|
||||
struct hv_per_cpu_context *hv_cpu;
|
||||
|
||||
/*
|
||||
* First, zero all per-cpu memory areas so hv_synic_free() can
|
||||
* detect what memory has been allocated and cleanup properly
|
||||
* after any failures.
|
||||
*/
|
||||
for_each_present_cpu(cpu) {
|
||||
hv_cpu = per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||
memset(hv_cpu, 0, sizeof(*hv_cpu));
|
||||
}
|
||||
|
||||
hv_context.hv_numa_map = kcalloc(nr_node_ids, sizeof(struct cpumask),
|
||||
GFP_KERNEL);
|
||||
|
@ -198,10 +209,8 @@ int hv_synic_alloc(void)
|
|||
}
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
struct hv_per_cpu_context *hv_cpu
|
||||
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||
hv_cpu = per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||
|
||||
memset(hv_cpu, 0, sizeof(*hv_cpu));
|
||||
tasklet_init(&hv_cpu->msg_dpc,
|
||||
vmbus_on_msg_dpc, (unsigned long) hv_cpu);
|
||||
|
||||
|
|
|
@ -689,7 +689,7 @@ static void hv_page_online_one(struct hv_hotadd_state *has, struct page *pg)
|
|||
__online_page_increment_counters(pg);
|
||||
__online_page_free(pg);
|
||||
|
||||
WARN_ON_ONCE(!spin_is_locked(&dm_device.ha_lock));
|
||||
lockdep_assert_held(&dm_device.ha_lock);
|
||||
dm_device.num_pages_onlined++;
|
||||
}
|
||||
|
||||
|
|
|
@ -353,7 +353,6 @@ static void process_ib_ipinfo(void *in_msg, void *out_msg, int op)
|
|||
|
||||
out->body.kvp_ip_val.dhcp_enabled = in->kvp_ip_val.dhcp_enabled;
|
||||
|
||||
default:
|
||||
utf16s_to_utf8s((wchar_t *)in->kvp_ip_val.adapter_id,
|
||||
MAX_ADAPTER_ID_SIZE,
|
||||
UTF16_LITTLE_ENDIAN,
|
||||
|
@ -406,7 +405,7 @@ kvp_send_key(struct work_struct *dummy)
|
|||
process_ib_ipinfo(in_msg, message, KVP_OP_SET_IP_INFO);
|
||||
break;
|
||||
case KVP_OP_GET_IP_INFO:
|
||||
process_ib_ipinfo(in_msg, message, KVP_OP_GET_IP_INFO);
|
||||
/* We only need to pass on message->kvp_hdr.operation. */
|
||||
break;
|
||||
case KVP_OP_SET:
|
||||
switch (in_msg->body.kvp_set.data.value_type) {
|
||||
|
@ -421,7 +420,7 @@ kvp_send_key(struct work_struct *dummy)
|
|||
UTF16_LITTLE_ENDIAN,
|
||||
message->body.kvp_set.data.value,
|
||||
HV_KVP_EXCHANGE_MAX_VALUE_SIZE - 1) + 1;
|
||||
break;
|
||||
break;
|
||||
|
||||
case REG_U32:
|
||||
/*
|
||||
|
@ -446,6 +445,9 @@ kvp_send_key(struct work_struct *dummy)
|
|||
break;
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KVP_OP_GET:
|
||||
message->body.kvp_set.data.key_size =
|
||||
utf16s_to_utf8s(
|
||||
|
@ -454,7 +456,7 @@ kvp_send_key(struct work_struct *dummy)
|
|||
UTF16_LITTLE_ENDIAN,
|
||||
message->body.kvp_set.data.key,
|
||||
HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1) + 1;
|
||||
break;
|
||||
break;
|
||||
|
||||
case KVP_OP_DELETE:
|
||||
message->body.kvp_delete.key_size =
|
||||
|
@ -464,12 +466,12 @@ kvp_send_key(struct work_struct *dummy)
|
|||
UTF16_LITTLE_ENDIAN,
|
||||
message->body.kvp_delete.key,
|
||||
HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1) + 1;
|
||||
break;
|
||||
break;
|
||||
|
||||
case KVP_OP_ENUMERATE:
|
||||
message->body.kvp_enum_data.index =
|
||||
in_msg->body.kvp_enum_data.index;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
kvp_transaction.state = HVUTIL_USERSPACE_REQ;
|
||||
|
|
|
@ -241,6 +241,7 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
|
|||
void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
|
||||
{
|
||||
vunmap(ring_info->ring_buffer);
|
||||
ring_info->ring_buffer = NULL;
|
||||
}
|
||||
|
||||
/* Write to the ring buffer. */
|
||||
|
|
|
@ -498,6 +498,54 @@ static ssize_t device_show(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RO(device);
|
||||
|
||||
static ssize_t driver_override_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct hv_device *hv_dev = device_to_hv_device(dev);
|
||||
char *driver_override, *old, *cp;
|
||||
|
||||
/* We need to keep extra room for a newline */
|
||||
if (count >= (PAGE_SIZE - 1))
|
||||
return -EINVAL;
|
||||
|
||||
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
||||
if (!driver_override)
|
||||
return -ENOMEM;
|
||||
|
||||
cp = strchr(driver_override, '\n');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
|
||||
device_lock(dev);
|
||||
old = hv_dev->driver_override;
|
||||
if (strlen(driver_override)) {
|
||||
hv_dev->driver_override = driver_override;
|
||||
} else {
|
||||
kfree(driver_override);
|
||||
hv_dev->driver_override = NULL;
|
||||
}
|
||||
device_unlock(dev);
|
||||
|
||||
kfree(old);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t driver_override_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hv_device *hv_dev = device_to_hv_device(dev);
|
||||
ssize_t len;
|
||||
|
||||
device_lock(dev);
|
||||
len = snprintf(buf, PAGE_SIZE, "%s\n", hv_dev->driver_override);
|
||||
device_unlock(dev);
|
||||
|
||||
return len;
|
||||
}
|
||||
static DEVICE_ATTR_RW(driver_override);
|
||||
|
||||
/* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */
|
||||
static struct attribute *vmbus_dev_attrs[] = {
|
||||
&dev_attr_id.attr,
|
||||
|
@ -528,6 +576,7 @@ static struct attribute *vmbus_dev_attrs[] = {
|
|||
&dev_attr_channel_vp_mapping.attr,
|
||||
&dev_attr_vendor.attr,
|
||||
&dev_attr_device.attr,
|
||||
&dev_attr_driver_override.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(vmbus_dev);
|
||||
|
@ -563,17 +612,26 @@ static inline bool is_null_guid(const uuid_le *guid)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a matching hv_vmbus_device_id pointer.
|
||||
* If there is no match, return NULL.
|
||||
*/
|
||||
static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv,
|
||||
const uuid_le *guid)
|
||||
static const struct hv_vmbus_device_id *
|
||||
hv_vmbus_dev_match(const struct hv_vmbus_device_id *id, const uuid_le *guid)
|
||||
|
||||
{
|
||||
if (id == NULL)
|
||||
return NULL; /* empty device table */
|
||||
|
||||
for (; !is_null_guid(&id->guid); id++)
|
||||
if (!uuid_le_cmp(id->guid, *guid))
|
||||
return id;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct hv_vmbus_device_id *
|
||||
hv_vmbus_dynid_match(struct hv_driver *drv, const uuid_le *guid)
|
||||
{
|
||||
const struct hv_vmbus_device_id *id = NULL;
|
||||
struct vmbus_dynid *dynid;
|
||||
|
||||
/* Look at the dynamic ids first, before the static ones */
|
||||
spin_lock(&drv->dynids.lock);
|
||||
list_for_each_entry(dynid, &drv->dynids.list, node) {
|
||||
if (!uuid_le_cmp(dynid->id.guid, *guid)) {
|
||||
|
@ -583,18 +641,37 @@ static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv,
|
|||
}
|
||||
spin_unlock(&drv->dynids.lock);
|
||||
|
||||
if (id)
|
||||
return id;
|
||||
return id;
|
||||
}
|
||||
|
||||
id = drv->id_table;
|
||||
if (id == NULL)
|
||||
return NULL; /* empty device table */
|
||||
static const struct hv_vmbus_device_id vmbus_device_null = {
|
||||
.guid = NULL_UUID_LE,
|
||||
};
|
||||
|
||||
for (; !is_null_guid(&id->guid); id++)
|
||||
if (!uuid_le_cmp(id->guid, *guid))
|
||||
return id;
|
||||
/*
|
||||
* Return a matching hv_vmbus_device_id pointer.
|
||||
* If there is no match, return NULL.
|
||||
*/
|
||||
static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv,
|
||||
struct hv_device *dev)
|
||||
{
|
||||
const uuid_le *guid = &dev->dev_type;
|
||||
const struct hv_vmbus_device_id *id;
|
||||
|
||||
return NULL;
|
||||
/* When driver_override is set, only bind to the matching driver */
|
||||
if (dev->driver_override && strcmp(dev->driver_override, drv->name))
|
||||
return NULL;
|
||||
|
||||
/* Look at the dynamic ids first, before the static ones */
|
||||
id = hv_vmbus_dynid_match(drv, guid);
|
||||
if (!id)
|
||||
id = hv_vmbus_dev_match(drv->id_table, guid);
|
||||
|
||||
/* driver_override will always match, send a dummy id */
|
||||
if (!id && dev->driver_override)
|
||||
id = &vmbus_device_null;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/* vmbus_add_dynid - add a new device ID to this driver and re-probe devices */
|
||||
|
@ -643,7 +720,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
|
|||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (hv_vmbus_get_id(drv, &guid))
|
||||
if (hv_vmbus_dynid_match(drv, &guid))
|
||||
return -EEXIST;
|
||||
|
||||
retval = vmbus_add_dynid(drv, &guid);
|
||||
|
@ -708,7 +785,7 @@ static int vmbus_match(struct device *device, struct device_driver *driver)
|
|||
if (is_hvsock_channel(hv_dev->channel))
|
||||
return drv->hvsock;
|
||||
|
||||
if (hv_vmbus_get_id(drv, &hv_dev->dev_type))
|
||||
if (hv_vmbus_get_id(drv, hv_dev))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
@ -725,7 +802,7 @@ static int vmbus_probe(struct device *child_device)
|
|||
struct hv_device *dev = device_to_hv_device(child_device);
|
||||
const struct hv_vmbus_device_id *dev_id;
|
||||
|
||||
dev_id = hv_vmbus_get_id(drv, &dev->dev_type);
|
||||
dev_id = hv_vmbus_get_id(drv, dev);
|
||||
if (drv->probe) {
|
||||
ret = drv->probe(dev, dev_id);
|
||||
if (ret != 0)
|
||||
|
@ -787,10 +864,9 @@ static void vmbus_device_release(struct device *device)
|
|||
struct vmbus_channel *channel = hv_dev->channel;
|
||||
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
hv_process_channel_removal(channel->offermsg.child_relid);
|
||||
hv_process_channel_removal(channel);
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
kfree(hv_dev);
|
||||
|
||||
}
|
||||
|
||||
/* The one and only one */
|
||||
|
|
|
@ -406,6 +406,7 @@ static inline int catu_wait_for_ready(struct catu_drvdata *drvdata)
|
|||
|
||||
static int catu_enable_hw(struct catu_drvdata *drvdata, void *data)
|
||||
{
|
||||
int rc;
|
||||
u32 control, mode;
|
||||
struct etr_buf *etr_buf = data;
|
||||
|
||||
|
@ -418,6 +419,10 @@ static int catu_enable_hw(struct catu_drvdata *drvdata, void *data)
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
rc = coresight_claim_device_unlocked(drvdata->base);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
control |= BIT(CATU_CONTROL_ENABLE);
|
||||
|
||||
if (etr_buf && etr_buf->mode == ETR_MODE_CATU) {
|
||||
|
@ -459,6 +464,7 @@ static int catu_disable_hw(struct catu_drvdata *drvdata)
|
|||
int rc = 0;
|
||||
|
||||
catu_write_control(drvdata, 0);
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
if (catu_wait_for_ready(drvdata)) {
|
||||
dev_info(drvdata->dev, "Timeout while waiting for READY\n");
|
||||
rc = -EAGAIN;
|
||||
|
|
|
@ -34,48 +34,87 @@ struct replicator_state {
|
|||
struct coresight_device *csdev;
|
||||
};
|
||||
|
||||
/*
|
||||
* replicator_reset : Reset the replicator configuration to sane values.
|
||||
*/
|
||||
static void replicator_reset(struct replicator_state *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
if (!coresight_claim_device_unlocked(drvdata->base)) {
|
||||
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER0);
|
||||
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER1);
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
}
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int replicator_enable(struct coresight_device *csdev, int inport,
|
||||
int outport)
|
||||
{
|
||||
int rc = 0;
|
||||
u32 reg;
|
||||
struct replicator_state *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
switch (outport) {
|
||||
case 0:
|
||||
reg = REPLICATOR_IDFILTER0;
|
||||
break;
|
||||
case 1:
|
||||
reg = REPLICATOR_IDFILTER1;
|
||||
break;
|
||||
default:
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/*
|
||||
* Ensure that the other port is disabled
|
||||
* 0x00 - passing through the replicator unimpeded
|
||||
* 0xff - disable (or impede) the flow of ATB data
|
||||
*/
|
||||
if (outport == 0) {
|
||||
writel_relaxed(0x00, drvdata->base + REPLICATOR_IDFILTER0);
|
||||
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER1);
|
||||
} else {
|
||||
writel_relaxed(0x00, drvdata->base + REPLICATOR_IDFILTER1);
|
||||
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER0);
|
||||
if ((readl_relaxed(drvdata->base + REPLICATOR_IDFILTER0) == 0xff) &&
|
||||
(readl_relaxed(drvdata->base + REPLICATOR_IDFILTER1) == 0xff))
|
||||
rc = coresight_claim_device_unlocked(drvdata->base);
|
||||
|
||||
/* Ensure that the outport is enabled. */
|
||||
if (!rc) {
|
||||
writel_relaxed(0x00, drvdata->base + reg);
|
||||
dev_dbg(drvdata->dev, "REPLICATOR enabled\n");
|
||||
}
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
dev_info(drvdata->dev, "REPLICATOR enabled\n");
|
||||
return 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void replicator_disable(struct coresight_device *csdev, int inport,
|
||||
int outport)
|
||||
{
|
||||
u32 reg;
|
||||
struct replicator_state *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
switch (outport) {
|
||||
case 0:
|
||||
reg = REPLICATOR_IDFILTER0;
|
||||
break;
|
||||
case 1:
|
||||
reg = REPLICATOR_IDFILTER1;
|
||||
break;
|
||||
default:
|
||||
WARN_ON(1);
|
||||
return;
|
||||
}
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* disable the flow of ATB data through port */
|
||||
if (outport == 0)
|
||||
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER0);
|
||||
else
|
||||
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER1);
|
||||
writel_relaxed(0xff, drvdata->base + reg);
|
||||
|
||||
if ((readl_relaxed(drvdata->base + REPLICATOR_IDFILTER0) == 0xff) &&
|
||||
(readl_relaxed(drvdata->base + REPLICATOR_IDFILTER1) == 0xff))
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
dev_info(drvdata->dev, "REPLICATOR disabled\n");
|
||||
dev_dbg(drvdata->dev, "REPLICATOR disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_link replicator_link_ops = {
|
||||
|
@ -156,7 +195,11 @@ static int replicator_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
desc.groups = replicator_groups;
|
||||
drvdata->csdev = coresight_register(&desc);
|
||||
|
||||
return PTR_ERR_OR_ZERO(drvdata->csdev);
|
||||
if (!IS_ERR(drvdata->csdev)) {
|
||||
replicator_reset(drvdata);
|
||||
return 0;
|
||||
}
|
||||
return PTR_ERR(drvdata->csdev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* Description: CoreSight Embedded Trace Buffer driver
|
||||
*/
|
||||
|
||||
#include <asm/local.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
|
@ -28,6 +27,7 @@
|
|||
|
||||
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-etm-perf.h"
|
||||
|
||||
#define ETB_RAM_DEPTH_REG 0x004
|
||||
#define ETB_STATUS_REG 0x00c
|
||||
|
@ -71,8 +71,8 @@
|
|||
* @miscdev: specifics to handle "/dev/xyz.etb" entry.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @reading: synchronise user space access to etb buffer.
|
||||
* @mode: this ETB is being used.
|
||||
* @buf: area of memory where ETB buffer content gets sent.
|
||||
* @mode: this ETB is being used.
|
||||
* @buffer_depth: size of @buf.
|
||||
* @trigger_cntr: amount of words to store after a trigger.
|
||||
*/
|
||||
|
@ -84,12 +84,15 @@ struct etb_drvdata {
|
|||
struct miscdevice miscdev;
|
||||
spinlock_t spinlock;
|
||||
local_t reading;
|
||||
local_t mode;
|
||||
u8 *buf;
|
||||
u32 mode;
|
||||
u32 buffer_depth;
|
||||
u32 trigger_cntr;
|
||||
};
|
||||
|
||||
static int etb_set_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle);
|
||||
|
||||
static unsigned int etb_get_buffer_depth(struct etb_drvdata *drvdata)
|
||||
{
|
||||
u32 depth = 0;
|
||||
|
@ -103,7 +106,7 @@ static unsigned int etb_get_buffer_depth(struct etb_drvdata *drvdata)
|
|||
return depth;
|
||||
}
|
||||
|
||||
static void etb_enable_hw(struct etb_drvdata *drvdata)
|
||||
static void __etb_enable_hw(struct etb_drvdata *drvdata)
|
||||
{
|
||||
int i;
|
||||
u32 depth;
|
||||
|
@ -131,32 +134,92 @@ static void etb_enable_hw(struct etb_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int etb_enable(struct coresight_device *csdev, u32 mode)
|
||||
static int etb_enable_hw(struct etb_drvdata *drvdata)
|
||||
{
|
||||
u32 val;
|
||||
__etb_enable_hw(drvdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int etb_enable_sysfs(struct coresight_device *csdev)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
val = local_cmpxchg(&drvdata->mode,
|
||||
CS_MODE_DISABLED, mode);
|
||||
/*
|
||||
* When accessing from Perf, a HW buffer can be handled
|
||||
* by a single trace entity. In sysFS mode many tracers
|
||||
* can be logging to the same HW buffer.
|
||||
*/
|
||||
if (val == CS_MODE_PERF)
|
||||
return -EBUSY;
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
/* Don't messup with perf sessions. */
|
||||
if (drvdata->mode == CS_MODE_PERF) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Nothing to do, the tracer is already enabled. */
|
||||
if (val == CS_MODE_SYSFS)
|
||||
if (drvdata->mode == CS_MODE_SYSFS)
|
||||
goto out;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
etb_enable_hw(drvdata);
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
ret = etb_enable_hw(drvdata);
|
||||
if (!ret)
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
|
||||
out:
|
||||
dev_info(drvdata->dev, "ETB enabled\n");
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int etb_enable_perf(struct coresight_device *csdev, void *data)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
/* No need to continue if the component is already in use. */
|
||||
if (drvdata->mode != CS_MODE_DISABLED) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't have an internal state to clean up if we fail to setup
|
||||
* the perf buffer. So we can perform the step before we turn the
|
||||
* ETB on and leave without cleaning up.
|
||||
*/
|
||||
ret = etb_set_buffer(csdev, (struct perf_output_handle *)data);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = etb_enable_hw(drvdata);
|
||||
if (!ret)
|
||||
drvdata->mode = CS_MODE_PERF;
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int etb_enable(struct coresight_device *csdev, u32 mode, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
switch (mode) {
|
||||
case CS_MODE_SYSFS:
|
||||
ret = etb_enable_sysfs(csdev);
|
||||
break;
|
||||
case CS_MODE_PERF:
|
||||
ret = etb_enable_perf(csdev, data);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(drvdata->dev, "ETB enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -256,13 +319,16 @@ static void etb_disable(struct coresight_device *csdev)
|
|||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
|
||||
/* Disable the ETB only if it needs to */
|
||||
if (drvdata->mode != CS_MODE_DISABLED) {
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
drvdata->mode = CS_MODE_DISABLED;
|
||||
}
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
local_set(&drvdata->mode, CS_MODE_DISABLED);
|
||||
|
||||
dev_info(drvdata->dev, "ETB disabled\n");
|
||||
dev_dbg(drvdata->dev, "ETB disabled\n");
|
||||
}
|
||||
|
||||
static void *etb_alloc_buffer(struct coresight_device *csdev, int cpu,
|
||||
|
@ -294,12 +360,14 @@ static void etb_free_buffer(void *config)
|
|||
}
|
||||
|
||||
static int etb_set_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
struct perf_output_handle *handle)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long head;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct cs_buffers *buf = etm_perf_sink_config(handle);
|
||||
|
||||
if (!buf)
|
||||
return -EINVAL;
|
||||
|
||||
/* wrap head around to the amount of space we have */
|
||||
head = handle->head & ((buf->nr_pages << PAGE_SHIFT) - 1);
|
||||
|
@ -315,37 +383,7 @@ static int etb_set_buffer(struct coresight_device *csdev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long etb_reset_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
unsigned long size = 0;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
if (buf) {
|
||||
/*
|
||||
* In snapshot mode ->data_size holds the new address of the
|
||||
* ring buffer's head. The size itself is the whole address
|
||||
* range since we want the latest information.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
handle->head = local_xchg(&buf->data_size,
|
||||
buf->nr_pages << PAGE_SHIFT);
|
||||
|
||||
/*
|
||||
* Tell the tracer PMU how much we got in this run and if
|
||||
* something went wrong along the way. Nobody else can use
|
||||
* this cs_buffers instance until we are done. As such
|
||||
* resetting parameters here and squaring off with the ring
|
||||
* buffer API in the tracer PMU is fine.
|
||||
*/
|
||||
size = local_xchg(&buf->data_size, 0);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void etb_update_buffer(struct coresight_device *csdev,
|
||||
static unsigned long etb_update_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
|
@ -354,13 +392,13 @@ static void etb_update_buffer(struct coresight_device *csdev,
|
|||
u8 *buf_ptr;
|
||||
const u32 *barrier;
|
||||
u32 read_ptr, write_ptr, capacity;
|
||||
u32 status, read_data, to_read;
|
||||
unsigned long offset;
|
||||
u32 status, read_data;
|
||||
unsigned long offset, to_read;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
capacity = drvdata->buffer_depth * ETB_FRAME_SIZE_WORDS;
|
||||
|
||||
|
@ -465,18 +503,17 @@ static void etb_update_buffer(struct coresight_device *csdev,
|
|||
writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER);
|
||||
|
||||
/*
|
||||
* In snapshot mode all we have to do is communicate to
|
||||
* perf_aux_output_end() the address of the current head. In full
|
||||
* trace mode the same function expects a size to move rb->aux_head
|
||||
* forward.
|
||||
* In snapshot mode we have to update the handle->head to point
|
||||
* to the new location.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
local_set(&buf->data_size, (cur * PAGE_SIZE) + offset);
|
||||
else
|
||||
local_add(to_read, &buf->data_size);
|
||||
|
||||
if (buf->snapshot) {
|
||||
handle->head = (cur * PAGE_SIZE) + offset;
|
||||
to_read = buf->nr_pages << PAGE_SHIFT;
|
||||
}
|
||||
etb_enable_hw(drvdata);
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink etb_sink_ops = {
|
||||
|
@ -484,8 +521,6 @@ static const struct coresight_ops_sink etb_sink_ops = {
|
|||
.disable = etb_disable,
|
||||
.alloc_buffer = etb_alloc_buffer,
|
||||
.free_buffer = etb_free_buffer,
|
||||
.set_buffer = etb_set_buffer,
|
||||
.reset_buffer = etb_reset_buffer,
|
||||
.update_buffer = etb_update_buffer,
|
||||
};
|
||||
|
||||
|
@ -498,14 +533,14 @@ static void etb_dump(struct etb_drvdata *drvdata)
|
|||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS) {
|
||||
if (drvdata->mode == CS_MODE_SYSFS) {
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
etb_enable_hw(drvdata);
|
||||
}
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "ETB dumped\n");
|
||||
dev_dbg(drvdata->dev, "ETB dumped\n");
|
||||
}
|
||||
|
||||
static int etb_open(struct inode *inode, struct file *file)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/percpu-defs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
@ -22,20 +23,6 @@
|
|||
static struct pmu etm_pmu;
|
||||
static bool etm_perf_up;
|
||||
|
||||
/**
|
||||
* struct etm_event_data - Coresight specifics associated to an event
|
||||
* @work: Handle to free allocated memory outside IRQ context.
|
||||
* @mask: Hold the CPU(s) this event was set for.
|
||||
* @snk_config: The sink configuration.
|
||||
* @path: An array of path, each slot for one CPU.
|
||||
*/
|
||||
struct etm_event_data {
|
||||
struct work_struct work;
|
||||
cpumask_t mask;
|
||||
void *snk_config;
|
||||
struct list_head **path;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct perf_output_handle, ctx_handle);
|
||||
static DEFINE_PER_CPU(struct coresight_device *, csdev_src);
|
||||
|
||||
|
@ -61,6 +48,18 @@ static const struct attribute_group *etm_pmu_attr_groups[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static inline struct list_head **
|
||||
etm_event_cpu_path_ptr(struct etm_event_data *data, int cpu)
|
||||
{
|
||||
return per_cpu_ptr(data->path, cpu);
|
||||
}
|
||||
|
||||
static inline struct list_head *
|
||||
etm_event_cpu_path(struct etm_event_data *data, int cpu)
|
||||
{
|
||||
return *etm_event_cpu_path_ptr(data, cpu);
|
||||
}
|
||||
|
||||
static void etm_event_read(struct perf_event *event) {}
|
||||
|
||||
static int etm_addr_filters_alloc(struct perf_event *event)
|
||||
|
@ -114,29 +113,30 @@ static void free_event_data(struct work_struct *work)
|
|||
|
||||
event_data = container_of(work, struct etm_event_data, work);
|
||||
mask = &event_data->mask;
|
||||
/*
|
||||
* First deal with the sink configuration. See comment in
|
||||
* etm_setup_aux() about why we take the first available path.
|
||||
*/
|
||||
if (event_data->snk_config) {
|
||||
|
||||
/* Free the sink buffers, if there are any */
|
||||
if (event_data->snk_config && !WARN_ON(cpumask_empty(mask))) {
|
||||
cpu = cpumask_first(mask);
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
sink = coresight_get_sink(etm_event_cpu_path(event_data, cpu));
|
||||
if (sink_ops(sink)->free_buffer)
|
||||
sink_ops(sink)->free_buffer(event_data->snk_config);
|
||||
}
|
||||
|
||||
for_each_cpu(cpu, mask) {
|
||||
if (!(IS_ERR_OR_NULL(event_data->path[cpu])))
|
||||
coresight_release_path(event_data->path[cpu]);
|
||||
struct list_head **ppath;
|
||||
|
||||
ppath = etm_event_cpu_path_ptr(event_data, cpu);
|
||||
if (!(IS_ERR_OR_NULL(*ppath)))
|
||||
coresight_release_path(*ppath);
|
||||
*ppath = NULL;
|
||||
}
|
||||
|
||||
kfree(event_data->path);
|
||||
free_percpu(event_data->path);
|
||||
kfree(event_data);
|
||||
}
|
||||
|
||||
static void *alloc_event_data(int cpu)
|
||||
{
|
||||
int size;
|
||||
cpumask_t *mask;
|
||||
struct etm_event_data *event_data;
|
||||
|
||||
|
@ -145,16 +145,12 @@ static void *alloc_event_data(int cpu)
|
|||
if (!event_data)
|
||||
return NULL;
|
||||
|
||||
/* Make sure nothing disappears under us */
|
||||
get_online_cpus();
|
||||
size = num_online_cpus();
|
||||
|
||||
mask = &event_data->mask;
|
||||
if (cpu != -1)
|
||||
cpumask_set_cpu(cpu, mask);
|
||||
else
|
||||
cpumask_copy(mask, cpu_online_mask);
|
||||
put_online_cpus();
|
||||
cpumask_copy(mask, cpu_present_mask);
|
||||
|
||||
/*
|
||||
* Each CPU has a single path between source and destination. As such
|
||||
|
@ -164,8 +160,8 @@ static void *alloc_event_data(int cpu)
|
|||
* unused memory when dealing with single CPU trace scenarios is small
|
||||
* compared to the cost of searching through an optimized array.
|
||||
*/
|
||||
event_data->path = kcalloc(size,
|
||||
sizeof(struct list_head *), GFP_KERNEL);
|
||||
event_data->path = alloc_percpu(struct list_head *);
|
||||
|
||||
if (!event_data->path) {
|
||||
kfree(event_data);
|
||||
return NULL;
|
||||
|
@ -206,34 +202,53 @@ static void *etm_setup_aux(int event_cpu, void **pages,
|
|||
* on the cmd line. As such the "enable_sink" flag in sysFS is reset.
|
||||
*/
|
||||
sink = coresight_get_enabled_sink(true);
|
||||
if (!sink)
|
||||
if (!sink || !sink_ops(sink)->alloc_buffer)
|
||||
goto err;
|
||||
|
||||
mask = &event_data->mask;
|
||||
|
||||
/* Setup the path for each CPU in a trace session */
|
||||
/*
|
||||
* Setup the path for each CPU in a trace session. We try to build
|
||||
* trace path for each CPU in the mask. If we don't find an ETM
|
||||
* for the CPU or fail to build a path, we clear the CPU from the
|
||||
* mask and continue with the rest. If ever we try to trace on those
|
||||
* CPUs, we can handle it and fail the session.
|
||||
*/
|
||||
for_each_cpu(cpu, mask) {
|
||||
struct list_head *path;
|
||||
struct coresight_device *csdev;
|
||||
|
||||
csdev = per_cpu(csdev_src, cpu);
|
||||
if (!csdev)
|
||||
goto err;
|
||||
/*
|
||||
* If there is no ETM associated with this CPU clear it from
|
||||
* the mask and continue with the rest. If ever we try to trace
|
||||
* on this CPU, we handle it accordingly.
|
||||
*/
|
||||
if (!csdev) {
|
||||
cpumask_clear_cpu(cpu, mask);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Building a path doesn't enable it, it simply builds a
|
||||
* list of devices from source to sink that can be
|
||||
* referenced later when the path is actually needed.
|
||||
*/
|
||||
event_data->path[cpu] = coresight_build_path(csdev, sink);
|
||||
if (IS_ERR(event_data->path[cpu]))
|
||||
goto err;
|
||||
path = coresight_build_path(csdev, sink);
|
||||
if (IS_ERR(path)) {
|
||||
cpumask_clear_cpu(cpu, mask);
|
||||
continue;
|
||||
}
|
||||
|
||||
*etm_event_cpu_path_ptr(event_data, cpu) = path;
|
||||
}
|
||||
|
||||
if (!sink_ops(sink)->alloc_buffer)
|
||||
/* If we don't have any CPUs ready for tracing, abort */
|
||||
cpu = cpumask_first(mask);
|
||||
if (cpu >= nr_cpu_ids)
|
||||
goto err;
|
||||
|
||||
cpu = cpumask_first(mask);
|
||||
/* Get the AUX specific data from the sink buffer */
|
||||
/* Allocate the sink buffer for this session */
|
||||
event_data->snk_config =
|
||||
sink_ops(sink)->alloc_buffer(sink, cpu, pages,
|
||||
nr_pages, overwrite);
|
||||
|
@ -255,6 +270,7 @@ static void etm_event_start(struct perf_event *event, int flags)
|
|||
struct etm_event_data *event_data;
|
||||
struct perf_output_handle *handle = this_cpu_ptr(&ctx_handle);
|
||||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
struct list_head *path;
|
||||
|
||||
if (!csdev)
|
||||
goto fail;
|
||||
|
@ -267,18 +283,14 @@ static void etm_event_start(struct perf_event *event, int flags)
|
|||
if (!event_data)
|
||||
goto fail;
|
||||
|
||||
path = etm_event_cpu_path(event_data, cpu);
|
||||
/* We need a sink, no need to continue without one */
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (WARN_ON_ONCE(!sink || !sink_ops(sink)->set_buffer))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Configure the sink */
|
||||
if (sink_ops(sink)->set_buffer(sink, handle,
|
||||
event_data->snk_config))
|
||||
sink = coresight_get_sink(path);
|
||||
if (WARN_ON_ONCE(!sink))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Nothing will happen without a path */
|
||||
if (coresight_enable_path(event_data->path[cpu], CS_MODE_PERF))
|
||||
if (coresight_enable_path(path, CS_MODE_PERF, handle))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Tell the perf core the event is alive */
|
||||
|
@ -286,11 +298,13 @@ static void etm_event_start(struct perf_event *event, int flags)
|
|||
|
||||
/* Finally enable the tracer */
|
||||
if (source_ops(csdev)->enable(csdev, event, CS_MODE_PERF))
|
||||
goto fail_end_stop;
|
||||
goto fail_disable_path;
|
||||
|
||||
out:
|
||||
return;
|
||||
|
||||
fail_disable_path:
|
||||
coresight_disable_path(path);
|
||||
fail_end_stop:
|
||||
perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
|
||||
perf_aux_output_end(handle, 0);
|
||||
|
@ -306,6 +320,7 @@ static void etm_event_stop(struct perf_event *event, int mode)
|
|||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
struct perf_output_handle *handle = this_cpu_ptr(&ctx_handle);
|
||||
struct etm_event_data *event_data = perf_get_aux(handle);
|
||||
struct list_head *path;
|
||||
|
||||
if (event->hw.state == PERF_HES_STOPPED)
|
||||
return;
|
||||
|
@ -313,7 +328,11 @@ static void etm_event_stop(struct perf_event *event, int mode)
|
|||
if (!csdev)
|
||||
return;
|
||||
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
path = etm_event_cpu_path(event_data, cpu);
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
sink = coresight_get_sink(path);
|
||||
if (!sink)
|
||||
return;
|
||||
|
||||
|
@ -331,20 +350,13 @@ static void etm_event_stop(struct perf_event *event, int mode)
|
|||
if (!sink_ops(sink)->update_buffer)
|
||||
return;
|
||||
|
||||
sink_ops(sink)->update_buffer(sink, handle,
|
||||
size = sink_ops(sink)->update_buffer(sink, handle,
|
||||
event_data->snk_config);
|
||||
|
||||
if (!sink_ops(sink)->reset_buffer)
|
||||
return;
|
||||
|
||||
size = sink_ops(sink)->reset_buffer(sink, handle,
|
||||
event_data->snk_config);
|
||||
|
||||
perf_aux_output_end(handle, size);
|
||||
}
|
||||
|
||||
/* Disabling the path make its elements available to other sessions */
|
||||
coresight_disable_path(event_data->path[cpu]);
|
||||
coresight_disable_path(path);
|
||||
}
|
||||
|
||||
static int etm_event_add(struct perf_event *event, int mode)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef _CORESIGHT_ETM_PERF_H
|
||||
#define _CORESIGHT_ETM_PERF_H
|
||||
|
||||
#include <linux/percpu-defs.h>
|
||||
#include "coresight-priv.h"
|
||||
|
||||
struct coresight_device;
|
||||
|
@ -42,14 +43,39 @@ struct etm_filters {
|
|||
bool ssstatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct etm_event_data - Coresight specifics associated to an event
|
||||
* @work: Handle to free allocated memory outside IRQ context.
|
||||
* @mask: Hold the CPU(s) this event was set for.
|
||||
* @snk_config: The sink configuration.
|
||||
* @path: An array of path, each slot for one CPU.
|
||||
*/
|
||||
struct etm_event_data {
|
||||
struct work_struct work;
|
||||
cpumask_t mask;
|
||||
void *snk_config;
|
||||
struct list_head * __percpu *path;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_CORESIGHT
|
||||
int etm_perf_symlink(struct coresight_device *csdev, bool link);
|
||||
static inline void *etm_perf_sink_config(struct perf_output_handle *handle)
|
||||
{
|
||||
struct etm_event_data *data = perf_get_aux(handle);
|
||||
|
||||
if (data)
|
||||
return data->snk_config;
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
static inline int etm_perf_symlink(struct coresight_device *csdev, bool link)
|
||||
{ return -EINVAL; }
|
||||
|
||||
static inline void *etm_perf_sink_config(struct perf_output_handle *handle)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_CORESIGHT */
|
||||
|
||||
#endif
|
||||
|
|
|
@ -355,11 +355,10 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void etm_enable_hw(void *info)
|
||||
static int etm_enable_hw(struct etm_drvdata *drvdata)
|
||||
{
|
||||
int i;
|
||||
int i, rc;
|
||||
u32 etmcr;
|
||||
struct etm_drvdata *drvdata = info;
|
||||
struct etm_config *config = &drvdata->config;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
@ -370,6 +369,9 @@ static void etm_enable_hw(void *info)
|
|||
etm_set_pwrup(drvdata);
|
||||
/* Make sure all registers are accessible */
|
||||
etm_os_unlock(drvdata);
|
||||
rc = coresight_claim_device_unlocked(drvdata->base);
|
||||
if (rc)
|
||||
goto done;
|
||||
|
||||
etm_set_prog(drvdata);
|
||||
|
||||
|
@ -418,9 +420,29 @@ static void etm_enable_hw(void *info)
|
|||
etm_writel(drvdata, 0x0, ETMVMIDCVR);
|
||||
|
||||
etm_clr_prog(drvdata);
|
||||
|
||||
done:
|
||||
if (rc)
|
||||
etm_set_pwrdwn(drvdata);
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
dev_dbg(drvdata->dev, "cpu: %d enable smp call done\n", drvdata->cpu);
|
||||
dev_dbg(drvdata->dev, "cpu: %d enable smp call done: %d\n",
|
||||
drvdata->cpu, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct etm_enable_arg {
|
||||
struct etm_drvdata *drvdata;
|
||||
int rc;
|
||||
};
|
||||
|
||||
static void etm_enable_hw_smp_call(void *info)
|
||||
{
|
||||
struct etm_enable_arg *arg = info;
|
||||
|
||||
if (WARN_ON(!arg))
|
||||
return;
|
||||
arg->rc = etm_enable_hw(arg->drvdata);
|
||||
}
|
||||
|
||||
static int etm_cpu_id(struct coresight_device *csdev)
|
||||
|
@ -475,14 +497,13 @@ static int etm_enable_perf(struct coresight_device *csdev,
|
|||
/* Configure the tracer based on the session's specifics */
|
||||
etm_parse_event_config(drvdata, event);
|
||||
/* And enable it */
|
||||
etm_enable_hw(drvdata);
|
||||
|
||||
return 0;
|
||||
return etm_enable_hw(drvdata);
|
||||
}
|
||||
|
||||
static int etm_enable_sysfs(struct coresight_device *csdev)
|
||||
{
|
||||
struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct etm_enable_arg arg = { 0 };
|
||||
int ret;
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
|
@ -492,20 +513,21 @@ static int etm_enable_sysfs(struct coresight_device *csdev)
|
|||
* hw configuration will take place on the local CPU during bring up.
|
||||
*/
|
||||
if (cpu_online(drvdata->cpu)) {
|
||||
arg.drvdata = drvdata;
|
||||
ret = smp_call_function_single(drvdata->cpu,
|
||||
etm_enable_hw, drvdata, 1);
|
||||
if (ret)
|
||||
goto err;
|
||||
etm_enable_hw_smp_call, &arg, 1);
|
||||
if (!ret)
|
||||
ret = arg.rc;
|
||||
if (!ret)
|
||||
drvdata->sticky_enable = true;
|
||||
} else {
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
drvdata->sticky_enable = true;
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_info(drvdata->dev, "ETM tracing enabled\n");
|
||||
return 0;
|
||||
|
||||
err:
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
if (!ret)
|
||||
dev_dbg(drvdata->dev, "ETM tracing enabled\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -555,6 +577,8 @@ static void etm_disable_hw(void *info)
|
|||
for (i = 0; i < drvdata->nr_cntr; i++)
|
||||
config->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i));
|
||||
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
|
||||
etm_set_pwrdwn(drvdata);
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
|
@ -604,7 +628,7 @@ static void etm_disable_sysfs(struct coresight_device *csdev)
|
|||
spin_unlock(&drvdata->spinlock);
|
||||
cpus_read_unlock();
|
||||
|
||||
dev_info(drvdata->dev, "ETM tracing disabled\n");
|
||||
dev_dbg(drvdata->dev, "ETM tracing disabled\n");
|
||||
}
|
||||
|
||||
static void etm_disable(struct coresight_device *csdev,
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <linux/pm_runtime.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/local.h>
|
||||
#include <asm/virt.h>
|
||||
|
||||
#include "coresight-etm4x.h"
|
||||
#include "coresight-etm-perf.h"
|
||||
|
@ -77,16 +78,24 @@ static int etm4_trace_id(struct coresight_device *csdev)
|
|||
return drvdata->trcid;
|
||||
}
|
||||
|
||||
static void etm4_enable_hw(void *info)
|
||||
struct etm4_enable_arg {
|
||||
struct etmv4_drvdata *drvdata;
|
||||
int rc;
|
||||
};
|
||||
|
||||
static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
|
||||
{
|
||||
int i;
|
||||
struct etmv4_drvdata *drvdata = info;
|
||||
int i, rc;
|
||||
struct etmv4_config *config = &drvdata->config;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
etm4_os_unlock(drvdata);
|
||||
|
||||
rc = coresight_claim_device_unlocked(drvdata->base);
|
||||
if (rc)
|
||||
goto done;
|
||||
|
||||
/* Disable the trace unit before programming trace registers */
|
||||
writel_relaxed(0, drvdata->base + TRCPRGCTLR);
|
||||
|
||||
|
@ -174,9 +183,21 @@ static void etm4_enable_hw(void *info)
|
|||
dev_err(drvdata->dev,
|
||||
"timeout while waiting for Idle Trace Status\n");
|
||||
|
||||
done:
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
dev_dbg(drvdata->dev, "cpu: %d enable smp call done\n", drvdata->cpu);
|
||||
dev_dbg(drvdata->dev, "cpu: %d enable smp call done: %d\n",
|
||||
drvdata->cpu, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void etm4_enable_hw_smp_call(void *info)
|
||||
{
|
||||
struct etm4_enable_arg *arg = info;
|
||||
|
||||
if (WARN_ON(!arg))
|
||||
return;
|
||||
arg->rc = etm4_enable_hw(arg->drvdata);
|
||||
}
|
||||
|
||||
static int etm4_parse_event_config(struct etmv4_drvdata *drvdata,
|
||||
|
@ -242,7 +263,7 @@ static int etm4_enable_perf(struct coresight_device *csdev,
|
|||
if (ret)
|
||||
goto out;
|
||||
/* And enable it */
|
||||
etm4_enable_hw(drvdata);
|
||||
ret = etm4_enable_hw(drvdata);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
|
@ -251,6 +272,7 @@ out:
|
|||
static int etm4_enable_sysfs(struct coresight_device *csdev)
|
||||
{
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct etm4_enable_arg arg = { 0 };
|
||||
int ret;
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
|
@ -259,19 +281,17 @@ static int etm4_enable_sysfs(struct coresight_device *csdev)
|
|||
* Executing etm4_enable_hw on the cpu whose ETM is being enabled
|
||||
* ensures that register writes occur when cpu is powered.
|
||||
*/
|
||||
arg.drvdata = drvdata;
|
||||
ret = smp_call_function_single(drvdata->cpu,
|
||||
etm4_enable_hw, drvdata, 1);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
drvdata->sticky_enable = true;
|
||||
etm4_enable_hw_smp_call, &arg, 1);
|
||||
if (!ret)
|
||||
ret = arg.rc;
|
||||
if (!ret)
|
||||
drvdata->sticky_enable = true;
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_info(drvdata->dev, "ETM tracing enabled\n");
|
||||
return 0;
|
||||
|
||||
err:
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
if (!ret)
|
||||
dev_dbg(drvdata->dev, "ETM tracing enabled\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -328,6 +348,8 @@ static void etm4_disable_hw(void *info)
|
|||
isb();
|
||||
writel_relaxed(control, drvdata->base + TRCPRGCTLR);
|
||||
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
dev_dbg(drvdata->dev, "cpu: %d disable smp call done\n", drvdata->cpu);
|
||||
|
@ -380,7 +402,7 @@ static void etm4_disable_sysfs(struct coresight_device *csdev)
|
|||
spin_unlock(&drvdata->spinlock);
|
||||
cpus_read_unlock();
|
||||
|
||||
dev_info(drvdata->dev, "ETM tracing disabled\n");
|
||||
dev_dbg(drvdata->dev, "ETM tracing disabled\n");
|
||||
}
|
||||
|
||||
static void etm4_disable(struct coresight_device *csdev,
|
||||
|
@ -605,7 +627,7 @@ static void etm4_set_default_config(struct etmv4_config *config)
|
|||
config->vinst_ctrl |= BIT(0);
|
||||
}
|
||||
|
||||
static u64 etm4_get_access_type(struct etmv4_config *config)
|
||||
static u64 etm4_get_ns_access_type(struct etmv4_config *config)
|
||||
{
|
||||
u64 access_type = 0;
|
||||
|
||||
|
@ -616,17 +638,26 @@ static u64 etm4_get_access_type(struct etmv4_config *config)
|
|||
* Bit[13] Exception level 1 - OS
|
||||
* Bit[14] Exception level 2 - Hypervisor
|
||||
* Bit[15] Never implemented
|
||||
*
|
||||
* Always stay away from hypervisor mode.
|
||||
*/
|
||||
access_type = ETM_EXLEVEL_NS_HYP;
|
||||
|
||||
if (config->mode & ETM_MODE_EXCL_KERN)
|
||||
access_type |= ETM_EXLEVEL_NS_OS;
|
||||
if (!is_kernel_in_hyp_mode()) {
|
||||
/* Stay away from hypervisor mode for non-VHE */
|
||||
access_type = ETM_EXLEVEL_NS_HYP;
|
||||
if (config->mode & ETM_MODE_EXCL_KERN)
|
||||
access_type |= ETM_EXLEVEL_NS_OS;
|
||||
} else if (config->mode & ETM_MODE_EXCL_KERN) {
|
||||
access_type = ETM_EXLEVEL_NS_HYP;
|
||||
}
|
||||
|
||||
if (config->mode & ETM_MODE_EXCL_USER)
|
||||
access_type |= ETM_EXLEVEL_NS_APP;
|
||||
|
||||
return access_type;
|
||||
}
|
||||
|
||||
static u64 etm4_get_access_type(struct etmv4_config *config)
|
||||
{
|
||||
u64 access_type = etm4_get_ns_access_type(config);
|
||||
|
||||
/*
|
||||
* EXLEVEL_S, bits[11:8], don't trace anything happening
|
||||
* in secure state.
|
||||
|
@ -880,20 +911,10 @@ void etm4_config_trace_mode(struct etmv4_config *config)
|
|||
|
||||
addr_acc = config->addr_acc[ETM_DEFAULT_ADDR_COMP];
|
||||
/* clear default config */
|
||||
addr_acc &= ~(ETM_EXLEVEL_NS_APP | ETM_EXLEVEL_NS_OS);
|
||||
addr_acc &= ~(ETM_EXLEVEL_NS_APP | ETM_EXLEVEL_NS_OS |
|
||||
ETM_EXLEVEL_NS_HYP);
|
||||
|
||||
/*
|
||||
* EXLEVEL_NS, bits[15:12]
|
||||
* The Exception levels are:
|
||||
* Bit[12] Exception level 0 - Application
|
||||
* Bit[13] Exception level 1 - OS
|
||||
* Bit[14] Exception level 2 - Hypervisor
|
||||
* Bit[15] Never implemented
|
||||
*/
|
||||
if (mode & ETM_MODE_EXCL_KERN)
|
||||
addr_acc |= ETM_EXLEVEL_NS_OS;
|
||||
else
|
||||
addr_acc |= ETM_EXLEVEL_NS_APP;
|
||||
addr_acc |= etm4_get_ns_access_type(config);
|
||||
|
||||
config->addr_acc[ETM_DEFAULT_ADDR_COMP] = addr_acc;
|
||||
config->addr_acc[ETM_DEFAULT_ADDR_COMP + 1] = addr_acc;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#define FUNNEL_HOLDTIME_MASK 0xf00
|
||||
#define FUNNEL_HOLDTIME_SHFT 0x8
|
||||
#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT)
|
||||
#define FUNNEL_ENSx_MASK 0xff
|
||||
|
||||
/**
|
||||
* struct funnel_drvdata - specifics associated to a funnel component
|
||||
|
@ -42,31 +43,42 @@ struct funnel_drvdata {
|
|||
unsigned long priority;
|
||||
};
|
||||
|
||||
static void funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
|
||||
static int funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
|
||||
{
|
||||
u32 functl;
|
||||
int rc = 0;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL);
|
||||
/* Claim the device only when we enable the first slave */
|
||||
if (!(functl & FUNNEL_ENSx_MASK)) {
|
||||
rc = coresight_claim_device_unlocked(drvdata->base);
|
||||
if (rc)
|
||||
goto done;
|
||||
}
|
||||
|
||||
functl &= ~FUNNEL_HOLDTIME_MASK;
|
||||
functl |= FUNNEL_HOLDTIME;
|
||||
functl |= (1 << port);
|
||||
writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
|
||||
writel_relaxed(drvdata->priority, drvdata->base + FUNNEL_PRICTL);
|
||||
|
||||
done:
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int funnel_enable(struct coresight_device *csdev, int inport,
|
||||
int outport)
|
||||
{
|
||||
int rc;
|
||||
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
funnel_enable_hw(drvdata, inport);
|
||||
rc = funnel_enable_hw(drvdata, inport);
|
||||
|
||||
dev_info(drvdata->dev, "FUNNEL inport %d enabled\n", inport);
|
||||
return 0;
|
||||
if (!rc)
|
||||
dev_dbg(drvdata->dev, "FUNNEL inport %d enabled\n", inport);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void funnel_disable_hw(struct funnel_drvdata *drvdata, int inport)
|
||||
|
@ -79,6 +91,10 @@ static void funnel_disable_hw(struct funnel_drvdata *drvdata, int inport)
|
|||
functl &= ~(1 << inport);
|
||||
writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL);
|
||||
|
||||
/* Disclaim the device if none of the slaves are now active */
|
||||
if (!(functl & FUNNEL_ENSx_MASK))
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
|
@ -89,7 +105,7 @@ static void funnel_disable(struct coresight_device *csdev, int inport,
|
|||
|
||||
funnel_disable_hw(drvdata, inport);
|
||||
|
||||
dev_info(drvdata->dev, "FUNNEL inport %d disabled\n", inport);
|
||||
dev_dbg(drvdata->dev, "FUNNEL inport %d disabled\n", inport);
|
||||
}
|
||||
|
||||
static const struct coresight_ops_link funnel_link_ops = {
|
||||
|
|
|
@ -25,6 +25,13 @@
|
|||
#define CORESIGHT_DEVID 0xfc8
|
||||
#define CORESIGHT_DEVTYPE 0xfcc
|
||||
|
||||
|
||||
/*
|
||||
* Coresight device CLAIM protocol.
|
||||
* See PSCI - ARM DEN 0022D, Section: 6.8.1 Debug and Trace save and restore.
|
||||
*/
|
||||
#define CORESIGHT_CLAIM_SELF_HOSTED BIT(1)
|
||||
|
||||
#define TIMEOUT_US 100
|
||||
#define BMVAL(val, lsb, msb) ((val & GENMASK(msb, lsb)) >> lsb)
|
||||
|
||||
|
@ -137,7 +144,7 @@ static inline void coresight_write_reg_pair(void __iomem *addr, u64 val,
|
|||
}
|
||||
|
||||
void coresight_disable_path(struct list_head *path);
|
||||
int coresight_enable_path(struct list_head *path, u32 mode);
|
||||
int coresight_enable_path(struct list_head *path, u32 mode, void *sink_data);
|
||||
struct coresight_device *coresight_get_sink(struct list_head *path);
|
||||
struct coresight_device *coresight_get_enabled_sink(bool reset);
|
||||
struct list_head *coresight_build_path(struct coresight_device *csdev,
|
||||
|
|
|
@ -35,7 +35,7 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
dev_info(drvdata->dev, "REPLICATOR enabled\n");
|
||||
dev_dbg(drvdata->dev, "REPLICATOR enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
dev_info(drvdata->dev, "REPLICATOR disabled\n");
|
||||
dev_dbg(drvdata->dev, "REPLICATOR disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_link replicator_link_ops = {
|
||||
|
|
|
@ -211,7 +211,7 @@ static int stm_enable(struct coresight_device *csdev,
|
|||
stm_enable_hw(drvdata);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_info(drvdata->dev, "STM tracing enabled\n");
|
||||
dev_dbg(drvdata->dev, "STM tracing enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -274,7 +274,7 @@ static void stm_disable(struct coresight_device *csdev,
|
|||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
local_set(&drvdata->mode, CS_MODE_DISABLED);
|
||||
dev_info(drvdata->dev, "STM tracing disabled\n");
|
||||
dev_dbg(drvdata->dev, "STM tracing disabled\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
#include <linux/slab.h>
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tmc.h"
|
||||
#include "coresight-etm-perf.h"
|
||||
|
||||
static void tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
static int tmc_set_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle);
|
||||
|
||||
static void __tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
|
@ -30,33 +34,41 @@ static void tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
int rc = coresight_claim_device(drvdata->base);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
__tmc_etb_enable_hw(drvdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
char *bufp;
|
||||
u32 read_data, lost;
|
||||
int i;
|
||||
|
||||
/* Check if the buffer wrapped around. */
|
||||
lost = readl_relaxed(drvdata->base + TMC_STS) & TMC_STS_FULL;
|
||||
bufp = drvdata->buf;
|
||||
drvdata->len = 0;
|
||||
while (1) {
|
||||
for (i = 0; i < drvdata->memwidth; i++) {
|
||||
read_data = readl_relaxed(drvdata->base + TMC_RRD);
|
||||
if (read_data == 0xFFFFFFFF)
|
||||
goto done;
|
||||
memcpy(bufp, &read_data, 4);
|
||||
bufp += 4;
|
||||
drvdata->len += 4;
|
||||
}
|
||||
read_data = readl_relaxed(drvdata->base + TMC_RRD);
|
||||
if (read_data == 0xFFFFFFFF)
|
||||
break;
|
||||
memcpy(bufp, &read_data, 4);
|
||||
bufp += 4;
|
||||
drvdata->len += 4;
|
||||
}
|
||||
done:
|
||||
|
||||
if (lost)
|
||||
coresight_insert_barrier_packet(drvdata->buf);
|
||||
return;
|
||||
}
|
||||
|
||||
static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
||||
static void __tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
|
@ -72,7 +84,13 @@ static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
coresight_disclaim_device(drvdata);
|
||||
__tmc_etb_disable_hw(drvdata);
|
||||
}
|
||||
|
||||
static void __tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
|
@ -88,13 +106,24 @@ static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
int rc = coresight_claim_device(drvdata->base);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
__tmc_etf_enable_hw(drvdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
coresight_disclaim_device_unlocked(drvdata->base);
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
|
@ -170,8 +199,12 @@ static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev)
|
|||
drvdata->buf = buf;
|
||||
}
|
||||
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
ret = tmc_etb_enable_hw(drvdata);
|
||||
if (!ret)
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
else
|
||||
/* Free up the buffer if we failed to enable */
|
||||
used = false;
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
|
@ -182,37 +215,40 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_sink_perf(struct coresight_device *csdev)
|
||||
static int tmc_enable_etf_sink_perf(struct coresight_device *csdev, void *data)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct perf_output_handle *handle = data;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
do {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (drvdata->reading)
|
||||
break;
|
||||
/*
|
||||
* In Perf mode there can be only one writer per sink. There
|
||||
* is also no need to continue if the ETB/ETF is already
|
||||
* operated from sysFS.
|
||||
*/
|
||||
if (drvdata->mode != CS_MODE_DISABLED)
|
||||
break;
|
||||
|
||||
/*
|
||||
* In Perf mode there can be only one writer per sink. There
|
||||
* is also no need to continue if the ETB/ETR is already operated
|
||||
* from sysFS.
|
||||
*/
|
||||
if (drvdata->mode != CS_MODE_DISABLED) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
drvdata->mode = CS_MODE_PERF;
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
out:
|
||||
ret = tmc_set_etf_buffer(csdev, handle);
|
||||
if (ret)
|
||||
break;
|
||||
ret = tmc_etb_enable_hw(drvdata);
|
||||
if (!ret)
|
||||
drvdata->mode = CS_MODE_PERF;
|
||||
} while (0);
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode)
|
||||
static int tmc_enable_etf_sink(struct coresight_device *csdev,
|
||||
u32 mode, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
@ -222,7 +258,7 @@ static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode)
|
|||
ret = tmc_enable_etf_sink_sysfs(csdev);
|
||||
break;
|
||||
case CS_MODE_PERF:
|
||||
ret = tmc_enable_etf_sink_perf(csdev);
|
||||
ret = tmc_enable_etf_sink_perf(csdev, data);
|
||||
break;
|
||||
/* We shouldn't be here */
|
||||
default:
|
||||
|
@ -233,7 +269,7 @@ static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETB/ETF enabled\n");
|
||||
dev_dbg(drvdata->dev, "TMC-ETB/ETF enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -256,12 +292,13 @@ static void tmc_disable_etf_sink(struct coresight_device *csdev)
|
|||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETB/ETF disabled\n");
|
||||
dev_dbg(drvdata->dev, "TMC-ETB/ETF disabled\n");
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_link(struct coresight_device *csdev,
|
||||
int inport, int outport)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
|
@ -271,12 +308,14 @@ static int tmc_enable_etf_link(struct coresight_device *csdev,
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
tmc_etf_enable_hw(drvdata);
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
ret = tmc_etf_enable_hw(drvdata);
|
||||
if (!ret)
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETF enabled\n");
|
||||
return 0;
|
||||
if (!ret)
|
||||
dev_dbg(drvdata->dev, "TMC-ETF enabled\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tmc_disable_etf_link(struct coresight_device *csdev,
|
||||
|
@ -295,7 +334,7 @@ static void tmc_disable_etf_link(struct coresight_device *csdev,
|
|||
drvdata->mode = CS_MODE_DISABLED;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETF disabled\n");
|
||||
dev_dbg(drvdata->dev, "TMC-ETF disabled\n");
|
||||
}
|
||||
|
||||
static void *tmc_alloc_etf_buffer(struct coresight_device *csdev, int cpu,
|
||||
|
@ -328,12 +367,14 @@ static void tmc_free_etf_buffer(void *config)
|
|||
}
|
||||
|
||||
static int tmc_set_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
struct perf_output_handle *handle)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long head;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct cs_buffers *buf = etm_perf_sink_config(handle);
|
||||
|
||||
if (!buf)
|
||||
return -EINVAL;
|
||||
|
||||
/* wrap head around to the amount of space we have */
|
||||
head = handle->head & ((buf->nr_pages << PAGE_SHIFT) - 1);
|
||||
|
@ -349,36 +390,7 @@ static int tmc_set_etf_buffer(struct coresight_device *csdev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long tmc_reset_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
long size = 0;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
if (buf) {
|
||||
/*
|
||||
* In snapshot mode ->data_size holds the new address of the
|
||||
* ring buffer's head. The size itself is the whole address
|
||||
* range since we want the latest information.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
handle->head = local_xchg(&buf->data_size,
|
||||
buf->nr_pages << PAGE_SHIFT);
|
||||
/*
|
||||
* Tell the tracer PMU how much we got in this run and if
|
||||
* something went wrong along the way. Nobody else can use
|
||||
* this cs_buffers instance until we are done. As such
|
||||
* resetting parameters here and squaring off with the ring
|
||||
* buffer API in the tracer PMU is fine.
|
||||
*/
|
||||
size = local_xchg(&buf->data_size, 0);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void tmc_update_etf_buffer(struct coresight_device *csdev,
|
||||
static unsigned long tmc_update_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
|
@ -387,17 +399,17 @@ static void tmc_update_etf_buffer(struct coresight_device *csdev,
|
|||
const u32 *barrier;
|
||||
u32 *buf_ptr;
|
||||
u64 read_ptr, write_ptr;
|
||||
u32 status, to_read;
|
||||
unsigned long offset;
|
||||
u32 status;
|
||||
unsigned long offset, to_read;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
/* This shouldn't happen */
|
||||
if (WARN_ON_ONCE(drvdata->mode != CS_MODE_PERF))
|
||||
return;
|
||||
return 0;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
|
@ -438,10 +450,10 @@ static void tmc_update_etf_buffer(struct coresight_device *csdev,
|
|||
case TMC_MEM_INTF_WIDTH_32BITS:
|
||||
case TMC_MEM_INTF_WIDTH_64BITS:
|
||||
case TMC_MEM_INTF_WIDTH_128BITS:
|
||||
mask = GENMASK(31, 5);
|
||||
mask = GENMASK(31, 4);
|
||||
break;
|
||||
case TMC_MEM_INTF_WIDTH_256BITS:
|
||||
mask = GENMASK(31, 6);
|
||||
mask = GENMASK(31, 5);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -486,18 +498,14 @@ static void tmc_update_etf_buffer(struct coresight_device *csdev,
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In snapshot mode all we have to do is communicate to
|
||||
* perf_aux_output_end() the address of the current head. In full
|
||||
* trace mode the same function expects a size to move rb->aux_head
|
||||
* forward.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
local_set(&buf->data_size, (cur * PAGE_SIZE) + offset);
|
||||
else
|
||||
local_add(to_read, &buf->data_size);
|
||||
|
||||
/* In snapshot mode we have to update the head */
|
||||
if (buf->snapshot) {
|
||||
handle->head = (cur * PAGE_SIZE) + offset;
|
||||
to_read = buf->nr_pages << PAGE_SHIFT;
|
||||
}
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tmc_etf_sink_ops = {
|
||||
|
@ -505,8 +513,6 @@ static const struct coresight_ops_sink tmc_etf_sink_ops = {
|
|||
.disable = tmc_disable_etf_sink,
|
||||
.alloc_buffer = tmc_alloc_etf_buffer,
|
||||
.free_buffer = tmc_free_etf_buffer,
|
||||
.set_buffer = tmc_set_etf_buffer,
|
||||
.reset_buffer = tmc_reset_etf_buffer,
|
||||
.update_buffer = tmc_update_etf_buffer,
|
||||
};
|
||||
|
||||
|
@ -563,7 +569,7 @@ int tmc_read_prepare_etb(struct tmc_drvdata *drvdata)
|
|||
|
||||
/* Disable the TMC if need be */
|
||||
if (drvdata->mode == CS_MODE_SYSFS)
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
__tmc_etb_disable_hw(drvdata);
|
||||
|
||||
drvdata->reading = true;
|
||||
out:
|
||||
|
@ -603,7 +609,7 @@ int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata)
|
|||
* can't be NULL.
|
||||
*/
|
||||
memset(drvdata->buf, 0, drvdata->size);
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
__tmc_etb_enable_hw(drvdata);
|
||||
} else {
|
||||
/*
|
||||
* The ETB/ETF is not tracing and the buffer was just read.
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "coresight-catu.h"
|
||||
#include "coresight-etm-perf.h"
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tmc.h"
|
||||
|
||||
|
@ -20,6 +21,28 @@ struct etr_flat_buf {
|
|||
size_t size;
|
||||
};
|
||||
|
||||
/*
|
||||
* etr_perf_buffer - Perf buffer used for ETR
|
||||
* @etr_buf - Actual buffer used by the ETR
|
||||
* @snaphost - Perf session mode
|
||||
* @head - handle->head at the beginning of the session.
|
||||
* @nr_pages - Number of pages in the ring buffer.
|
||||
* @pages - Array of Pages in the ring buffer.
|
||||
*/
|
||||
struct etr_perf_buffer {
|
||||
struct etr_buf *etr_buf;
|
||||
bool snapshot;
|
||||
unsigned long head;
|
||||
int nr_pages;
|
||||
void **pages;
|
||||
};
|
||||
|
||||
/* Convert the perf index to an offset within the ETR buffer */
|
||||
#define PERF_IDX2OFF(idx, buf) ((idx) % ((buf)->nr_pages << PAGE_SHIFT))
|
||||
|
||||
/* Lower limit for ETR hardware buffer */
|
||||
#define TMC_ETR_PERF_MIN_BUF_SIZE SZ_1M
|
||||
|
||||
/*
|
||||
* The TMC ETR SG has a page size of 4K. The SG table contains pointers
|
||||
* to 4KB buffers. However, the OS may use a PAGE_SIZE different from
|
||||
|
@ -536,7 +559,7 @@ tmc_init_etr_sg_table(struct device *dev, int node,
|
|||
sg_table = tmc_alloc_sg_table(dev, node, nr_tpages, nr_dpages, pages);
|
||||
if (IS_ERR(sg_table)) {
|
||||
kfree(etr_table);
|
||||
return ERR_PTR(PTR_ERR(sg_table));
|
||||
return ERR_CAST(sg_table);
|
||||
}
|
||||
|
||||
etr_table->sg_table = sg_table;
|
||||
|
@ -728,12 +751,14 @@ tmc_etr_get_catu_device(struct tmc_drvdata *drvdata)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static inline void tmc_etr_enable_catu(struct tmc_drvdata *drvdata)
|
||||
static inline int tmc_etr_enable_catu(struct tmc_drvdata *drvdata,
|
||||
struct etr_buf *etr_buf)
|
||||
{
|
||||
struct coresight_device *catu = tmc_etr_get_catu_device(drvdata);
|
||||
|
||||
if (catu && helper_ops(catu)->enable)
|
||||
helper_ops(catu)->enable(catu, drvdata->etr_buf);
|
||||
return helper_ops(catu)->enable(catu, etr_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void tmc_etr_disable_catu(struct tmc_drvdata *drvdata)
|
||||
|
@ -895,17 +920,11 @@ static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata)
|
|||
tmc_etr_buf_insert_barrier_packet(etr_buf, etr_buf->offset);
|
||||
}
|
||||
|
||||
static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
static void __tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 axictl, sts;
|
||||
struct etr_buf *etr_buf = drvdata->etr_buf;
|
||||
|
||||
/*
|
||||
* If this ETR is connected to a CATU, enable it before we turn
|
||||
* this on
|
||||
*/
|
||||
tmc_etr_enable_catu(drvdata);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
|
@ -924,11 +943,8 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
|||
axictl |= TMC_AXICTL_ARCACHE_OS;
|
||||
}
|
||||
|
||||
if (etr_buf->mode == ETR_MODE_ETR_SG) {
|
||||
if (WARN_ON(!tmc_etr_has_cap(drvdata, TMC_ETR_SG)))
|
||||
return;
|
||||
if (etr_buf->mode == ETR_MODE_ETR_SG)
|
||||
axictl |= TMC_AXICTL_SCT_GAT_MODE;
|
||||
}
|
||||
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
tmc_write_dba(drvdata, etr_buf->hwaddr);
|
||||
|
@ -954,19 +970,54 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tmc_etr_enable_hw(struct tmc_drvdata *drvdata,
|
||||
struct etr_buf *etr_buf)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Callers should provide an appropriate buffer for use */
|
||||
if (WARN_ON(!etr_buf))
|
||||
return -EINVAL;
|
||||
|
||||
if ((etr_buf->mode == ETR_MODE_ETR_SG) &&
|
||||
WARN_ON(!tmc_etr_has_cap(drvdata, TMC_ETR_SG)))
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON(drvdata->etr_buf))
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* If this ETR is connected to a CATU, enable it before we turn
|
||||
* this on.
|
||||
*/
|
||||
rc = tmc_etr_enable_catu(drvdata, etr_buf);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = coresight_claim_device(drvdata->base);
|
||||
if (!rc) {
|
||||
drvdata->etr_buf = etr_buf;
|
||||
__tmc_etr_enable_hw(drvdata);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the available trace data in the buffer (starts at etr_buf->offset,
|
||||
* limited by etr_buf->len) from @pos, with a maximum limit of @len,
|
||||
* also updating the @bufpp on where to find it. Since the trace data
|
||||
* starts at anywhere in the buffer, depending on the RRP, we adjust the
|
||||
* @len returned to handle buffer wrapping around.
|
||||
*
|
||||
* We are protected here by drvdata->reading != 0, which ensures the
|
||||
* sysfs_buf stays alive.
|
||||
*/
|
||||
ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
|
||||
loff_t pos, size_t len, char **bufpp)
|
||||
{
|
||||
s64 offset;
|
||||
ssize_t actual = len;
|
||||
struct etr_buf *etr_buf = drvdata->etr_buf;
|
||||
struct etr_buf *etr_buf = drvdata->sysfs_buf;
|
||||
|
||||
if (pos + actual > etr_buf->len)
|
||||
actual = etr_buf->len - pos;
|
||||
|
@ -996,10 +1047,17 @@ tmc_etr_free_sysfs_buf(struct etr_buf *buf)
|
|||
|
||||
static void tmc_etr_sync_sysfs_buf(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
tmc_sync_etr_buf(drvdata);
|
||||
struct etr_buf *etr_buf = drvdata->etr_buf;
|
||||
|
||||
if (WARN_ON(drvdata->sysfs_buf != etr_buf)) {
|
||||
tmc_etr_free_sysfs_buf(drvdata->sysfs_buf);
|
||||
drvdata->sysfs_buf = NULL;
|
||||
} else {
|
||||
tmc_sync_etr_buf(drvdata);
|
||||
}
|
||||
}
|
||||
|
||||
static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
|
||||
static void __tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
|
@ -1015,8 +1073,16 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
|
|||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
}
|
||||
|
||||
static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
__tmc_etr_disable_hw(drvdata);
|
||||
/* Disable CATU device if this ETR is connected to one */
|
||||
tmc_etr_disable_catu(drvdata);
|
||||
coresight_disclaim_device(drvdata->base);
|
||||
/* Reset the ETR buf used by hardware */
|
||||
drvdata->etr_buf = NULL;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
|
||||
|
@ -1024,7 +1090,7 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
|
|||
int ret = 0;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct etr_buf *new_buf = NULL, *free_buf = NULL;
|
||||
struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
|
||||
|
||||
/*
|
||||
* If we are enabling the ETR from disabled state, we need to make
|
||||
|
@ -1035,7 +1101,8 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
|
|||
* with the lock released.
|
||||
*/
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (!drvdata->etr_buf || (drvdata->etr_buf->size != drvdata->size)) {
|
||||
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
|
||||
if (!sysfs_buf || (sysfs_buf->size != drvdata->size)) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Allocate memory with the locks released */
|
||||
|
@ -1064,14 +1131,15 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
|
|||
* If we don't have a buffer or it doesn't match the requested size,
|
||||
* use the buffer allocated above. Otherwise reuse the existing buffer.
|
||||
*/
|
||||
if (!drvdata->etr_buf ||
|
||||
(new_buf && drvdata->etr_buf->size != new_buf->size)) {
|
||||
free_buf = drvdata->etr_buf;
|
||||
drvdata->etr_buf = new_buf;
|
||||
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
|
||||
if (!sysfs_buf || (new_buf && sysfs_buf->size != new_buf->size)) {
|
||||
free_buf = sysfs_buf;
|
||||
drvdata->sysfs_buf = new_buf;
|
||||
}
|
||||
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
ret = tmc_etr_enable_hw(drvdata, drvdata->sysfs_buf);
|
||||
if (!ret)
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
|
@ -1080,24 +1148,244 @@ out:
|
|||
tmc_etr_free_sysfs_buf(free_buf);
|
||||
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC-ETR enabled\n");
|
||||
dev_dbg(drvdata->dev, "TMC-ETR enabled\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink_perf(struct coresight_device *csdev)
|
||||
/*
|
||||
* tmc_etr_setup_perf_buf: Allocate ETR buffer for use by perf.
|
||||
* The size of the hardware buffer is dependent on the size configured
|
||||
* via sysfs and the perf ring buffer size. We prefer to allocate the
|
||||
* largest possible size, scaling down the size by half until it
|
||||
* reaches a minimum limit (1M), beyond which we give up.
|
||||
*/
|
||||
static struct etr_perf_buffer *
|
||||
tmc_etr_setup_perf_buf(struct tmc_drvdata *drvdata, int node, int nr_pages,
|
||||
void **pages, bool snapshot)
|
||||
{
|
||||
/* We don't support perf mode yet ! */
|
||||
return -EINVAL;
|
||||
struct etr_buf *etr_buf;
|
||||
struct etr_perf_buffer *etr_perf;
|
||||
unsigned long size;
|
||||
|
||||
etr_perf = kzalloc_node(sizeof(*etr_perf), GFP_KERNEL, node);
|
||||
if (!etr_perf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/*
|
||||
* Try to match the perf ring buffer size if it is larger
|
||||
* than the size requested via sysfs.
|
||||
*/
|
||||
if ((nr_pages << PAGE_SHIFT) > drvdata->size) {
|
||||
etr_buf = tmc_alloc_etr_buf(drvdata, (nr_pages << PAGE_SHIFT),
|
||||
0, node, NULL);
|
||||
if (!IS_ERR(etr_buf))
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* Else switch to configured size for this ETR
|
||||
* and scale down until we hit the minimum limit.
|
||||
*/
|
||||
size = drvdata->size;
|
||||
do {
|
||||
etr_buf = tmc_alloc_etr_buf(drvdata, size, 0, node, NULL);
|
||||
if (!IS_ERR(etr_buf))
|
||||
goto done;
|
||||
size /= 2;
|
||||
} while (size >= TMC_ETR_PERF_MIN_BUF_SIZE);
|
||||
|
||||
kfree(etr_perf);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
done:
|
||||
etr_perf->etr_buf = etr_buf;
|
||||
return etr_perf;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink(struct coresight_device *csdev, u32 mode)
|
||||
|
||||
static void *tmc_alloc_etr_buffer(struct coresight_device *csdev,
|
||||
int cpu, void **pages, int nr_pages,
|
||||
bool snapshot)
|
||||
{
|
||||
struct etr_perf_buffer *etr_perf;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (cpu == -1)
|
||||
cpu = smp_processor_id();
|
||||
|
||||
etr_perf = tmc_etr_setup_perf_buf(drvdata, cpu_to_node(cpu),
|
||||
nr_pages, pages, snapshot);
|
||||
if (IS_ERR(etr_perf)) {
|
||||
dev_dbg(drvdata->dev, "Unable to allocate ETR buffer\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
etr_perf->snapshot = snapshot;
|
||||
etr_perf->nr_pages = nr_pages;
|
||||
etr_perf->pages = pages;
|
||||
|
||||
return etr_perf;
|
||||
}
|
||||
|
||||
static void tmc_free_etr_buffer(void *config)
|
||||
{
|
||||
struct etr_perf_buffer *etr_perf = config;
|
||||
|
||||
if (etr_perf->etr_buf)
|
||||
tmc_free_etr_buf(etr_perf->etr_buf);
|
||||
kfree(etr_perf);
|
||||
}
|
||||
|
||||
/*
|
||||
* tmc_etr_sync_perf_buffer: Copy the actual trace data from the hardware
|
||||
* buffer to the perf ring buffer.
|
||||
*/
|
||||
static void tmc_etr_sync_perf_buffer(struct etr_perf_buffer *etr_perf)
|
||||
{
|
||||
long bytes, to_copy;
|
||||
long pg_idx, pg_offset, src_offset;
|
||||
unsigned long head = etr_perf->head;
|
||||
char **dst_pages, *src_buf;
|
||||
struct etr_buf *etr_buf = etr_perf->etr_buf;
|
||||
|
||||
head = etr_perf->head;
|
||||
pg_idx = head >> PAGE_SHIFT;
|
||||
pg_offset = head & (PAGE_SIZE - 1);
|
||||
dst_pages = (char **)etr_perf->pages;
|
||||
src_offset = etr_buf->offset;
|
||||
to_copy = etr_buf->len;
|
||||
|
||||
while (to_copy > 0) {
|
||||
/*
|
||||
* In one iteration, we can copy minimum of :
|
||||
* 1) what is available in the source buffer,
|
||||
* 2) what is available in the source buffer, before it
|
||||
* wraps around.
|
||||
* 3) what is available in the destination page.
|
||||
* in one iteration.
|
||||
*/
|
||||
bytes = tmc_etr_buf_get_data(etr_buf, src_offset, to_copy,
|
||||
&src_buf);
|
||||
if (WARN_ON_ONCE(bytes <= 0))
|
||||
break;
|
||||
bytes = min(bytes, (long)(PAGE_SIZE - pg_offset));
|
||||
|
||||
memcpy(dst_pages[pg_idx] + pg_offset, src_buf, bytes);
|
||||
|
||||
to_copy -= bytes;
|
||||
|
||||
/* Move destination pointers */
|
||||
pg_offset += bytes;
|
||||
if (pg_offset == PAGE_SIZE) {
|
||||
pg_offset = 0;
|
||||
if (++pg_idx == etr_perf->nr_pages)
|
||||
pg_idx = 0;
|
||||
}
|
||||
|
||||
/* Move source pointers */
|
||||
src_offset += bytes;
|
||||
if (src_offset >= etr_buf->size)
|
||||
src_offset -= etr_buf->size;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* tmc_update_etr_buffer : Update the perf ring buffer with the
|
||||
* available trace data. We use software double buffering at the moment.
|
||||
*
|
||||
* TODO: Add support for reusing the perf ring buffer.
|
||||
*/
|
||||
static unsigned long
|
||||
tmc_update_etr_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *config)
|
||||
{
|
||||
bool lost = false;
|
||||
unsigned long flags, size = 0;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct etr_perf_buffer *etr_perf = config;
|
||||
struct etr_buf *etr_buf = etr_perf->etr_buf;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (WARN_ON(drvdata->perf_data != etr_perf)) {
|
||||
lost = true;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
goto out;
|
||||
}
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
tmc_sync_etr_buf(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
/* Reset perf specific data */
|
||||
drvdata->perf_data = NULL;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
size = etr_buf->len;
|
||||
tmc_etr_sync_perf_buffer(etr_perf);
|
||||
|
||||
/*
|
||||
* Update handle->head in snapshot mode. Also update the size to the
|
||||
* hardware buffer size if there was an overflow.
|
||||
*/
|
||||
if (etr_perf->snapshot) {
|
||||
handle->head += size;
|
||||
if (etr_buf->full)
|
||||
size = etr_buf->size;
|
||||
}
|
||||
|
||||
lost |= etr_buf->full;
|
||||
out:
|
||||
if (lost)
|
||||
perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
|
||||
return size;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
|
||||
{
|
||||
int rc = 0;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct perf_output_handle *handle = data;
|
||||
struct etr_perf_buffer *etr_perf = etm_perf_sink_config(handle);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
/*
|
||||
* There can be only one writer per sink in perf mode. If the sink
|
||||
* is already open in SYSFS mode, we can't use it.
|
||||
*/
|
||||
if (drvdata->mode != CS_MODE_DISABLED || WARN_ON(drvdata->perf_data)) {
|
||||
rc = -EBUSY;
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
if (WARN_ON(!etr_perf || !etr_perf->etr_buf)) {
|
||||
rc = -EINVAL;
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
etr_perf->head = PERF_IDX2OFF(handle->head, etr_perf);
|
||||
drvdata->perf_data = etr_perf;
|
||||
rc = tmc_etr_enable_hw(drvdata, etr_perf->etr_buf);
|
||||
if (!rc)
|
||||
drvdata->mode = CS_MODE_PERF;
|
||||
|
||||
unlock_out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink(struct coresight_device *csdev,
|
||||
u32 mode, void *data)
|
||||
{
|
||||
switch (mode) {
|
||||
case CS_MODE_SYSFS:
|
||||
return tmc_enable_etr_sink_sysfs(csdev);
|
||||
case CS_MODE_PERF:
|
||||
return tmc_enable_etr_sink_perf(csdev);
|
||||
return tmc_enable_etr_sink_perf(csdev, data);
|
||||
}
|
||||
|
||||
/* We shouldn't be here */
|
||||
|
@ -1123,12 +1411,15 @@ static void tmc_disable_etr_sink(struct coresight_device *csdev)
|
|||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETR disabled\n");
|
||||
dev_dbg(drvdata->dev, "TMC-ETR disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tmc_etr_sink_ops = {
|
||||
.enable = tmc_enable_etr_sink,
|
||||
.disable = tmc_disable_etr_sink,
|
||||
.alloc_buffer = tmc_alloc_etr_buffer,
|
||||
.update_buffer = tmc_update_etr_buffer,
|
||||
.free_buffer = tmc_free_etr_buffer,
|
||||
};
|
||||
|
||||
const struct coresight_ops tmc_etr_cs_ops = {
|
||||
|
@ -1150,21 +1441,19 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
|
|||
goto out;
|
||||
}
|
||||
|
||||
/* Don't interfere if operated from Perf */
|
||||
if (drvdata->mode == CS_MODE_PERF) {
|
||||
/*
|
||||
* We can safely allow reads even if the ETR is operating in PERF mode,
|
||||
* since the sysfs session is captured in mode specific data.
|
||||
* If drvdata::sysfs_data is NULL the trace data has been read already.
|
||||
*/
|
||||
if (!drvdata->sysfs_buf) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If drvdata::etr_buf is NULL the trace data has been read already */
|
||||
if (drvdata->etr_buf == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Disable the TMC if need be */
|
||||
/* Disable the TMC if we are trying to read from a running session. */
|
||||
if (drvdata->mode == CS_MODE_SYSFS)
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
__tmc_etr_disable_hw(drvdata);
|
||||
|
||||
drvdata->reading = true;
|
||||
out:
|
||||
|
@ -1176,7 +1465,7 @@ out:
|
|||
int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct etr_buf *etr_buf = NULL;
|
||||
struct etr_buf *sysfs_buf = NULL;
|
||||
|
||||
/* config types are set a boot time and never change */
|
||||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
|
||||
|
@ -1191,22 +1480,22 @@ int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
|
|||
* buffer. Since the tracer is still enabled drvdata::buf can't
|
||||
* be NULL.
|
||||
*/
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
__tmc_etr_enable_hw(drvdata);
|
||||
} else {
|
||||
/*
|
||||
* The ETR is not tracing and the buffer was just read.
|
||||
* As such prepare to free the trace buffer.
|
||||
*/
|
||||
etr_buf = drvdata->etr_buf;
|
||||
drvdata->etr_buf = NULL;
|
||||
sysfs_buf = drvdata->sysfs_buf;
|
||||
drvdata->sysfs_buf = NULL;
|
||||
}
|
||||
|
||||
drvdata->reading = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Free allocated memory out side of the spinlock */
|
||||
if (etr_buf)
|
||||
tmc_free_etr_buf(etr_buf);
|
||||
if (sysfs_buf)
|
||||
tmc_etr_free_sysfs_buf(sysfs_buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ static int tmc_read_prepare(struct tmc_drvdata *drvdata)
|
|||
}
|
||||
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC read start\n");
|
||||
dev_dbg(drvdata->dev, "TMC read start\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ static int tmc_read_unprepare(struct tmc_drvdata *drvdata)
|
|||
}
|
||||
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC read end\n");
|
||||
dev_dbg(drvdata->dev, "TMC read end\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -170,6 +170,8 @@ struct etr_buf {
|
|||
* @trigger_cntr: amount of words to store after a trigger.
|
||||
* @etr_caps: Bitmask of capabilities of the TMC ETR, inferred from the
|
||||
* device configuration register (DEVID)
|
||||
* @perf_data: PERF buffer for ETR.
|
||||
* @sysfs_data: SYSFS buffer for ETR.
|
||||
*/
|
||||
struct tmc_drvdata {
|
||||
void __iomem *base;
|
||||
|
@ -189,6 +191,8 @@ struct tmc_drvdata {
|
|||
enum tmc_mem_intf_width memwidth;
|
||||
u32 trigger_cntr;
|
||||
u32 etr_caps;
|
||||
struct etr_buf *sysfs_buf;
|
||||
void *perf_data;
|
||||
};
|
||||
|
||||
struct etr_buf_operations {
|
||||
|
|
|
@ -68,13 +68,13 @@ static void tpiu_enable_hw(struct tpiu_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tpiu_enable(struct coresight_device *csdev, u32 mode)
|
||||
static int tpiu_enable(struct coresight_device *csdev, u32 mode, void *__unused)
|
||||
{
|
||||
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
tpiu_enable_hw(drvdata);
|
||||
|
||||
dev_info(drvdata->dev, "TPIU enabled\n");
|
||||
dev_dbg(drvdata->dev, "TPIU enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ static void tpiu_disable(struct coresight_device *csdev)
|
|||
|
||||
tpiu_disable_hw(drvdata);
|
||||
|
||||
dev_info(drvdata->dev, "TPIU disabled\n");
|
||||
dev_dbg(drvdata->dev, "TPIU disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tpiu_sink_ops = {
|
||||
|
|
|
@ -128,16 +128,105 @@ static int coresight_find_link_outport(struct coresight_device *csdev,
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int coresight_enable_sink(struct coresight_device *csdev, u32 mode)
|
||||
static inline u32 coresight_read_claim_tags(void __iomem *base)
|
||||
{
|
||||
return readl_relaxed(base + CORESIGHT_CLAIMCLR);
|
||||
}
|
||||
|
||||
static inline bool coresight_is_claimed_self_hosted(void __iomem *base)
|
||||
{
|
||||
return coresight_read_claim_tags(base) == CORESIGHT_CLAIM_SELF_HOSTED;
|
||||
}
|
||||
|
||||
static inline bool coresight_is_claimed_any(void __iomem *base)
|
||||
{
|
||||
return coresight_read_claim_tags(base) != 0;
|
||||
}
|
||||
|
||||
static inline void coresight_set_claim_tags(void __iomem *base)
|
||||
{
|
||||
writel_relaxed(CORESIGHT_CLAIM_SELF_HOSTED, base + CORESIGHT_CLAIMSET);
|
||||
isb();
|
||||
}
|
||||
|
||||
static inline void coresight_clear_claim_tags(void __iomem *base)
|
||||
{
|
||||
writel_relaxed(CORESIGHT_CLAIM_SELF_HOSTED, base + CORESIGHT_CLAIMCLR);
|
||||
isb();
|
||||
}
|
||||
|
||||
/*
|
||||
* coresight_claim_device_unlocked : Claim the device for self-hosted usage
|
||||
* to prevent an external tool from touching this device. As per PSCI
|
||||
* standards, section "Preserving the execution context" => "Debug and Trace
|
||||
* save and Restore", DBGCLAIM[1] is reserved for Self-hosted debug/trace and
|
||||
* DBGCLAIM[0] is reserved for external tools.
|
||||
*
|
||||
* Called with CS_UNLOCKed for the component.
|
||||
* Returns : 0 on success
|
||||
*/
|
||||
int coresight_claim_device_unlocked(void __iomem *base)
|
||||
{
|
||||
if (coresight_is_claimed_any(base))
|
||||
return -EBUSY;
|
||||
|
||||
coresight_set_claim_tags(base);
|
||||
if (coresight_is_claimed_self_hosted(base))
|
||||
return 0;
|
||||
/* There was a race setting the tags, clean up and fail */
|
||||
coresight_clear_claim_tags(base);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
int coresight_claim_device(void __iomem *base)
|
||||
{
|
||||
int rc;
|
||||
|
||||
CS_UNLOCK(base);
|
||||
rc = coresight_claim_device_unlocked(base);
|
||||
CS_LOCK(base);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* coresight_disclaim_device_unlocked : Clear the claim tags for the device.
|
||||
* Called with CS_UNLOCKed for the component.
|
||||
*/
|
||||
void coresight_disclaim_device_unlocked(void __iomem *base)
|
||||
{
|
||||
|
||||
if (coresight_is_claimed_self_hosted(base))
|
||||
coresight_clear_claim_tags(base);
|
||||
else
|
||||
/*
|
||||
* The external agent may have not honoured our claim
|
||||
* and has manipulated it. Or something else has seriously
|
||||
* gone wrong in our driver.
|
||||
*/
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
|
||||
void coresight_disclaim_device(void __iomem *base)
|
||||
{
|
||||
CS_UNLOCK(base);
|
||||
coresight_disclaim_device_unlocked(base);
|
||||
CS_LOCK(base);
|
||||
}
|
||||
|
||||
static int coresight_enable_sink(struct coresight_device *csdev,
|
||||
u32 mode, void *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!csdev->enable) {
|
||||
if (sink_ops(csdev)->enable) {
|
||||
ret = sink_ops(csdev)->enable(csdev, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* We need to make sure the "new" session is compatible with the
|
||||
* existing "mode" of operation.
|
||||
*/
|
||||
if (sink_ops(csdev)->enable) {
|
||||
ret = sink_ops(csdev)->enable(csdev, mode, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
csdev->enable = true;
|
||||
}
|
||||
|
||||
|
@ -184,8 +273,10 @@ static int coresight_enable_link(struct coresight_device *csdev,
|
|||
if (atomic_inc_return(&csdev->refcnt[refport]) == 1) {
|
||||
if (link_ops(csdev)->enable) {
|
||||
ret = link_ops(csdev)->enable(csdev, inport, outport);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
atomic_dec(&csdev->refcnt[refport]);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,13 +365,21 @@ static bool coresight_disable_source(struct coresight_device *csdev)
|
|||
return !csdev->enable;
|
||||
}
|
||||
|
||||
void coresight_disable_path(struct list_head *path)
|
||||
/*
|
||||
* coresight_disable_path_from : Disable components in the given path beyond
|
||||
* @nd in the list. If @nd is NULL, all the components, except the SOURCE are
|
||||
* disabled.
|
||||
*/
|
||||
static void coresight_disable_path_from(struct list_head *path,
|
||||
struct coresight_node *nd)
|
||||
{
|
||||
u32 type;
|
||||
struct coresight_node *nd;
|
||||
struct coresight_device *csdev, *parent, *child;
|
||||
|
||||
list_for_each_entry(nd, path, link) {
|
||||
if (!nd)
|
||||
nd = list_first_entry(path, struct coresight_node, link);
|
||||
|
||||
list_for_each_entry_continue(nd, path, link) {
|
||||
csdev = nd->csdev;
|
||||
type = csdev->type;
|
||||
|
||||
|
@ -300,7 +399,12 @@ void coresight_disable_path(struct list_head *path)
|
|||
coresight_disable_sink(csdev);
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_SOURCE:
|
||||
/* sources are disabled from either sysFS or Perf */
|
||||
/*
|
||||
* We skip the first node in the path assuming that it
|
||||
* is the source. So we don't expect a source device in
|
||||
* the middle of a path.
|
||||
*/
|
||||
WARN_ON(1);
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_LINK:
|
||||
parent = list_prev_entry(nd, link)->csdev;
|
||||
|
@ -313,7 +417,12 @@ void coresight_disable_path(struct list_head *path)
|
|||
}
|
||||
}
|
||||
|
||||
int coresight_enable_path(struct list_head *path, u32 mode)
|
||||
void coresight_disable_path(struct list_head *path)
|
||||
{
|
||||
coresight_disable_path_from(path, NULL);
|
||||
}
|
||||
|
||||
int coresight_enable_path(struct list_head *path, u32 mode, void *sink_data)
|
||||
{
|
||||
|
||||
int ret = 0;
|
||||
|
@ -338,9 +447,15 @@ int coresight_enable_path(struct list_head *path, u32 mode)
|
|||
|
||||
switch (type) {
|
||||
case CORESIGHT_DEV_TYPE_SINK:
|
||||
ret = coresight_enable_sink(csdev, mode);
|
||||
ret = coresight_enable_sink(csdev, mode, sink_data);
|
||||
/*
|
||||
* Sink is the first component turned on. If we
|
||||
* failed to enable the sink, there are no components
|
||||
* that need disabling. Disabling the path here
|
||||
* would mean we could disrupt an existing session.
|
||||
*/
|
||||
if (ret)
|
||||
goto err;
|
||||
goto out;
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_SOURCE:
|
||||
/* sources are enabled from either sysFS or Perf */
|
||||
|
@ -360,7 +475,7 @@ int coresight_enable_path(struct list_head *path, u32 mode)
|
|||
out:
|
||||
return ret;
|
||||
err:
|
||||
coresight_disable_path(path);
|
||||
coresight_disable_path_from(path, nd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@ -635,7 +750,7 @@ int coresight_enable(struct coresight_device *csdev)
|
|||
goto out;
|
||||
}
|
||||
|
||||
ret = coresight_enable_path(path, CS_MODE_SYSFS);
|
||||
ret = coresight_enable_path(path, CS_MODE_SYSFS, NULL);
|
||||
if (ret)
|
||||
goto err_path;
|
||||
|
||||
|
@ -995,18 +1110,16 @@ postcore_initcall(coresight_init);
|
|||
|
||||
struct coresight_device *coresight_register(struct coresight_desc *desc)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
int link_subtype;
|
||||
int nr_refcnts = 1;
|
||||
atomic_t *refcnts = NULL;
|
||||
struct coresight_device *csdev;
|
||||
struct coresight_connection *conns = NULL;
|
||||
|
||||
csdev = kzalloc(sizeof(*csdev), GFP_KERNEL);
|
||||
if (!csdev) {
|
||||
ret = -ENOMEM;
|
||||
goto err_kzalloc_csdev;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (desc->type == CORESIGHT_DEV_TYPE_LINK ||
|
||||
|
@ -1022,7 +1135,7 @@ struct coresight_device *coresight_register(struct coresight_desc *desc)
|
|||
refcnts = kcalloc(nr_refcnts, sizeof(*refcnts), GFP_KERNEL);
|
||||
if (!refcnts) {
|
||||
ret = -ENOMEM;
|
||||
goto err_kzalloc_refcnts;
|
||||
goto err_free_csdev;
|
||||
}
|
||||
|
||||
csdev->refcnt = refcnts;
|
||||
|
@ -1030,22 +1143,7 @@ struct coresight_device *coresight_register(struct coresight_desc *desc)
|
|||
csdev->nr_inport = desc->pdata->nr_inport;
|
||||
csdev->nr_outport = desc->pdata->nr_outport;
|
||||
|
||||
/* Initialise connections if there is at least one outport */
|
||||
if (csdev->nr_outport) {
|
||||
conns = kcalloc(csdev->nr_outport, sizeof(*conns), GFP_KERNEL);
|
||||
if (!conns) {
|
||||
ret = -ENOMEM;
|
||||
goto err_kzalloc_conns;
|
||||
}
|
||||
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conns[i].outport = desc->pdata->outports[i];
|
||||
conns[i].child_name = desc->pdata->child_names[i];
|
||||
conns[i].child_port = desc->pdata->child_ports[i];
|
||||
}
|
||||
}
|
||||
|
||||
csdev->conns = conns;
|
||||
csdev->conns = desc->pdata->conns;
|
||||
|
||||
csdev->type = desc->type;
|
||||
csdev->subtype = desc->subtype;
|
||||
|
@ -1062,7 +1160,11 @@ struct coresight_device *coresight_register(struct coresight_desc *desc)
|
|||
ret = device_register(&csdev->dev);
|
||||
if (ret) {
|
||||
put_device(&csdev->dev);
|
||||
goto err_kzalloc_csdev;
|
||||
/*
|
||||
* All resources are free'd explicitly via
|
||||
* coresight_device_release(), triggered from put_device().
|
||||
*/
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
mutex_lock(&coresight_mutex);
|
||||
|
@ -1074,11 +1176,9 @@ struct coresight_device *coresight_register(struct coresight_desc *desc)
|
|||
|
||||
return csdev;
|
||||
|
||||
err_kzalloc_conns:
|
||||
kfree(refcnts);
|
||||
err_kzalloc_refcnts:
|
||||
err_free_csdev:
|
||||
kfree(csdev);
|
||||
err_kzalloc_csdev:
|
||||
err_out:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_register);
|
||||
|
|
|
@ -45,8 +45,13 @@ of_coresight_get_endpoint_device(struct device_node *endpoint)
|
|||
endpoint, of_dev_node_match);
|
||||
}
|
||||
|
||||
static void of_coresight_get_ports(const struct device_node *node,
|
||||
int *nr_inport, int *nr_outport)
|
||||
static inline bool of_coresight_legacy_ep_is_input(struct device_node *ep)
|
||||
{
|
||||
return of_property_read_bool(ep, "slave-mode");
|
||||
}
|
||||
|
||||
static void of_coresight_get_ports_legacy(const struct device_node *node,
|
||||
int *nr_inport, int *nr_outport)
|
||||
{
|
||||
struct device_node *ep = NULL;
|
||||
int in = 0, out = 0;
|
||||
|
@ -56,7 +61,7 @@ static void of_coresight_get_ports(const struct device_node *node,
|
|||
if (!ep)
|
||||
break;
|
||||
|
||||
if (of_property_read_bool(ep, "slave-mode"))
|
||||
if (of_coresight_legacy_ep_is_input(ep))
|
||||
in++;
|
||||
else
|
||||
out++;
|
||||
|
@ -67,32 +72,77 @@ static void of_coresight_get_ports(const struct device_node *node,
|
|||
*nr_outport = out;
|
||||
}
|
||||
|
||||
static struct device_node *of_coresight_get_port_parent(struct device_node *ep)
|
||||
{
|
||||
struct device_node *parent = of_graph_get_port_parent(ep);
|
||||
|
||||
/*
|
||||
* Skip one-level up to the real device node, if we
|
||||
* are using the new bindings.
|
||||
*/
|
||||
if (!of_node_cmp(parent->name, "in-ports") ||
|
||||
!of_node_cmp(parent->name, "out-ports"))
|
||||
parent = of_get_next_parent(parent);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
static inline struct device_node *
|
||||
of_coresight_get_input_ports_node(const struct device_node *node)
|
||||
{
|
||||
return of_get_child_by_name(node, "in-ports");
|
||||
}
|
||||
|
||||
static inline struct device_node *
|
||||
of_coresight_get_output_ports_node(const struct device_node *node)
|
||||
{
|
||||
return of_get_child_by_name(node, "out-ports");
|
||||
}
|
||||
|
||||
static inline int
|
||||
of_coresight_count_ports(struct device_node *port_parent)
|
||||
{
|
||||
int i = 0;
|
||||
struct device_node *ep = NULL;
|
||||
|
||||
while ((ep = of_graph_get_next_endpoint(port_parent, ep)))
|
||||
i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
static void of_coresight_get_ports(const struct device_node *node,
|
||||
int *nr_inport, int *nr_outport)
|
||||
{
|
||||
struct device_node *input_ports = NULL, *output_ports = NULL;
|
||||
|
||||
input_ports = of_coresight_get_input_ports_node(node);
|
||||
output_ports = of_coresight_get_output_ports_node(node);
|
||||
|
||||
if (input_ports || output_ports) {
|
||||
if (input_ports) {
|
||||
*nr_inport = of_coresight_count_ports(input_ports);
|
||||
of_node_put(input_ports);
|
||||
}
|
||||
if (output_ports) {
|
||||
*nr_outport = of_coresight_count_ports(output_ports);
|
||||
of_node_put(output_ports);
|
||||
}
|
||||
} else {
|
||||
/* Fall back to legacy DT bindings parsing */
|
||||
of_coresight_get_ports_legacy(node, nr_inport, nr_outport);
|
||||
}
|
||||
}
|
||||
|
||||
static int of_coresight_alloc_memory(struct device *dev,
|
||||
struct coresight_platform_data *pdata)
|
||||
{
|
||||
/* List of output port on this component */
|
||||
pdata->outports = devm_kcalloc(dev,
|
||||
pdata->nr_outport,
|
||||
sizeof(*pdata->outports),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->outports)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Children connected to this component via @outports */
|
||||
pdata->child_names = devm_kcalloc(dev,
|
||||
pdata->nr_outport,
|
||||
sizeof(*pdata->child_names),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->child_names)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Port number on the child this component is connected to */
|
||||
pdata->child_ports = devm_kcalloc(dev,
|
||||
pdata->nr_outport,
|
||||
sizeof(*pdata->child_ports),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->child_ports)
|
||||
return -ENOMEM;
|
||||
if (pdata->nr_outport) {
|
||||
pdata->conns = devm_kzalloc(dev, pdata->nr_outport *
|
||||
sizeof(*pdata->conns),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->conns)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -114,17 +164,78 @@ int of_coresight_get_cpu(const struct device_node *node)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(of_coresight_get_cpu);
|
||||
|
||||
/*
|
||||
* of_coresight_parse_endpoint : Parse the given output endpoint @ep
|
||||
* and fill the connection information in @conn
|
||||
*
|
||||
* Parses the local port, remote device name and the remote port.
|
||||
*
|
||||
* Returns :
|
||||
* 1 - If the parsing is successful and a connection record
|
||||
* was created for an output connection.
|
||||
* 0 - If the parsing completed without any fatal errors.
|
||||
* -Errno - Fatal error, abort the scanning.
|
||||
*/
|
||||
static int of_coresight_parse_endpoint(struct device *dev,
|
||||
struct device_node *ep,
|
||||
struct coresight_connection *conn)
|
||||
{
|
||||
int ret = 0;
|
||||
struct of_endpoint endpoint, rendpoint;
|
||||
struct device_node *rparent = NULL;
|
||||
struct device_node *rep = NULL;
|
||||
struct device *rdev = NULL;
|
||||
|
||||
do {
|
||||
/* Parse the local port details */
|
||||
if (of_graph_parse_endpoint(ep, &endpoint))
|
||||
break;
|
||||
/*
|
||||
* Get a handle on the remote endpoint and the device it is
|
||||
* attached to.
|
||||
*/
|
||||
rep = of_graph_get_remote_endpoint(ep);
|
||||
if (!rep)
|
||||
break;
|
||||
rparent = of_coresight_get_port_parent(rep);
|
||||
if (!rparent)
|
||||
break;
|
||||
if (of_graph_parse_endpoint(rep, &rendpoint))
|
||||
break;
|
||||
|
||||
/* If the remote device is not available, defer probing */
|
||||
rdev = of_coresight_get_endpoint_device(rparent);
|
||||
if (!rdev) {
|
||||
ret = -EPROBE_DEFER;
|
||||
break;
|
||||
}
|
||||
|
||||
conn->outport = endpoint.port;
|
||||
conn->child_name = devm_kstrdup(dev,
|
||||
dev_name(rdev),
|
||||
GFP_KERNEL);
|
||||
conn->child_port = rendpoint.port;
|
||||
/* Connection record updated */
|
||||
ret = 1;
|
||||
} while (0);
|
||||
|
||||
of_node_put(rparent);
|
||||
of_node_put(rep);
|
||||
put_device(rdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct coresight_platform_data *
|
||||
of_get_coresight_platform_data(struct device *dev,
|
||||
const struct device_node *node)
|
||||
{
|
||||
int i = 0, ret = 0;
|
||||
int ret = 0;
|
||||
struct coresight_platform_data *pdata;
|
||||
struct of_endpoint endpoint, rendpoint;
|
||||
struct device *rdev;
|
||||
struct coresight_connection *conn;
|
||||
struct device_node *ep = NULL;
|
||||
struct device_node *rparent = NULL;
|
||||
struct device_node *rport = NULL;
|
||||
const struct device_node *parent = NULL;
|
||||
bool legacy_binding = false;
|
||||
|
||||
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
|
@ -132,63 +243,54 @@ of_get_coresight_platform_data(struct device *dev,
|
|||
|
||||
/* Use device name as sysfs handle */
|
||||
pdata->name = dev_name(dev);
|
||||
pdata->cpu = of_coresight_get_cpu(node);
|
||||
|
||||
/* Get the number of input and output port for this component */
|
||||
of_coresight_get_ports(node, &pdata->nr_inport, &pdata->nr_outport);
|
||||
|
||||
if (pdata->nr_outport) {
|
||||
ret = of_coresight_alloc_memory(dev, pdata);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
/* If there are no output connections, we are done */
|
||||
if (!pdata->nr_outport)
|
||||
return pdata;
|
||||
|
||||
/* Iterate through each port to discover topology */
|
||||
do {
|
||||
/* Get a handle on a port */
|
||||
ep = of_graph_get_next_endpoint(node, ep);
|
||||
if (!ep)
|
||||
break;
|
||||
ret = of_coresight_alloc_memory(dev, pdata);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
/*
|
||||
* No need to deal with input ports, processing for as
|
||||
* processing for output ports will deal with them.
|
||||
*/
|
||||
if (of_find_property(ep, "slave-mode", NULL))
|
||||
continue;
|
||||
|
||||
/* Get a handle on the local endpoint */
|
||||
ret = of_graph_parse_endpoint(ep, &endpoint);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
/* The local out port number */
|
||||
pdata->outports[i] = endpoint.port;
|
||||
|
||||
/*
|
||||
* Get a handle on the remote port and parent
|
||||
* attached to it.
|
||||
*/
|
||||
rparent = of_graph_get_remote_port_parent(ep);
|
||||
rport = of_graph_get_remote_port(ep);
|
||||
|
||||
if (!rparent || !rport)
|
||||
continue;
|
||||
|
||||
if (of_graph_parse_endpoint(rport, &rendpoint))
|
||||
continue;
|
||||
|
||||
rdev = of_coresight_get_endpoint_device(rparent);
|
||||
if (!rdev)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
pdata->child_names[i] = dev_name(rdev);
|
||||
pdata->child_ports[i] = rendpoint.id;
|
||||
|
||||
i++;
|
||||
} while (ep);
|
||||
parent = of_coresight_get_output_ports_node(node);
|
||||
/*
|
||||
* If the DT uses obsoleted bindings, the ports are listed
|
||||
* under the device and we need to filter out the input
|
||||
* ports.
|
||||
*/
|
||||
if (!parent) {
|
||||
legacy_binding = true;
|
||||
parent = node;
|
||||
dev_warn_once(dev, "Uses obsolete Coresight DT bindings\n");
|
||||
}
|
||||
|
||||
pdata->cpu = of_coresight_get_cpu(node);
|
||||
conn = pdata->conns;
|
||||
|
||||
/* Iterate through each output port to discover topology */
|
||||
while ((ep = of_graph_get_next_endpoint(parent, ep))) {
|
||||
/*
|
||||
* Legacy binding mixes input/output ports under the
|
||||
* same parent. So, skip the input ports if we are dealing
|
||||
* with legacy binding, as they processed with their
|
||||
* connected output ports.
|
||||
*/
|
||||
if (legacy_binding && of_coresight_legacy_ep_is_input(ep))
|
||||
continue;
|
||||
|
||||
ret = of_coresight_parse_endpoint(dev, ep, conn);
|
||||
switch (ret) {
|
||||
case 1:
|
||||
conn++; /* Fall through */
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
}
|
||||
|
||||
return pdata;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,35 @@ config STM
|
|||
|
||||
if STM
|
||||
|
||||
config STM_PROTO_BASIC
|
||||
tristate "Basic STM framing protocol driver"
|
||||
default CONFIG_STM
|
||||
help
|
||||
This is a simple framing protocol for sending data over STM
|
||||
devices. This was the protocol that the STM framework used
|
||||
exclusively until the MIPI SyS-T support was added. Use this
|
||||
driver for compatibility with your existing STM setup.
|
||||
|
||||
The receiving side only needs to be able to decode the MIPI
|
||||
STP protocol in order to extract the data.
|
||||
|
||||
If you want to be able to use the basic protocol or want the
|
||||
backwards compatibility for your existing setup, say Y.
|
||||
|
||||
config STM_PROTO_SYS_T
|
||||
tristate "MIPI SyS-T STM framing protocol driver"
|
||||
default CONFIG_STM
|
||||
help
|
||||
This is an implementation of MIPI SyS-T protocol to be used
|
||||
over the STP transport. In addition to the data payload, it
|
||||
also carries additional metadata for time correlation, better
|
||||
means of trace source identification, etc.
|
||||
|
||||
The receiving side must be able to decode this protocol in
|
||||
addition to the MIPI STP, in order to extract the data.
|
||||
|
||||
If you don't know what this is, say N.
|
||||
|
||||
config STM_DUMMY
|
||||
tristate "Dummy STM driver"
|
||||
help
|
||||
|
|
|
@ -3,6 +3,12 @@ obj-$(CONFIG_STM) += stm_core.o
|
|||
|
||||
stm_core-y := core.o policy.o
|
||||
|
||||
obj-$(CONFIG_STM_PROTO_BASIC) += stm_p_basic.o
|
||||
obj-$(CONFIG_STM_PROTO_SYS_T) += stm_p_sys-t.o
|
||||
|
||||
stm_p_basic-y := p_basic.o
|
||||
stm_p_sys-t-y := p_sys-t.o
|
||||
|
||||
obj-$(CONFIG_STM_DUMMY) += dummy_stm.o
|
||||
|
||||
obj-$(CONFIG_STM_SOURCE_CONSOLE) += stm_console.o
|
||||
|
|
|
@ -293,15 +293,15 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
if (width > stm->data->sw_nchannels)
|
||||
return -EINVAL;
|
||||
|
||||
if (policy_node) {
|
||||
stp_policy_node_get_ranges(policy_node,
|
||||
&midx, &mend, &cidx, &cend);
|
||||
} else {
|
||||
midx = stm->data->sw_start;
|
||||
cidx = 0;
|
||||
mend = stm->data->sw_end;
|
||||
cend = stm->data->sw_nchannels - 1;
|
||||
}
|
||||
/* We no longer accept policy_node==NULL here */
|
||||
if (WARN_ON_ONCE(!policy_node))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Also, the caller holds reference to policy_node, so it won't
|
||||
* disappear on us.
|
||||
*/
|
||||
stp_policy_node_get_ranges(policy_node, &midx, &mend, &cidx, &cend);
|
||||
|
||||
spin_lock(&stm->mc_lock);
|
||||
spin_lock(&output->lock);
|
||||
|
@ -316,11 +316,26 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
output->master = midx;
|
||||
output->channel = cidx;
|
||||
output->nr_chans = width;
|
||||
if (stm->pdrv->output_open) {
|
||||
void *priv = stp_policy_node_priv(policy_node);
|
||||
|
||||
if (WARN_ON_ONCE(!priv))
|
||||
goto unlock;
|
||||
|
||||
/* configfs subsys mutex is held by the caller */
|
||||
ret = stm->pdrv->output_open(priv, output);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
stm_output_claim(stm, output);
|
||||
dev_dbg(&stm->dev, "assigned %u:%u (+%u)\n", midx, cidx, width);
|
||||
|
||||
ret = 0;
|
||||
unlock:
|
||||
if (ret)
|
||||
output->nr_chans = 0;
|
||||
|
||||
spin_unlock(&output->lock);
|
||||
spin_unlock(&stm->mc_lock);
|
||||
|
||||
|
@ -333,6 +348,8 @@ static void stm_output_free(struct stm_device *stm, struct stm_output *output)
|
|||
spin_lock(&output->lock);
|
||||
if (output->nr_chans)
|
||||
stm_output_disclaim(stm, output);
|
||||
if (stm->pdrv && stm->pdrv->output_close)
|
||||
stm->pdrv->output_close(output);
|
||||
spin_unlock(&output->lock);
|
||||
spin_unlock(&stm->mc_lock);
|
||||
}
|
||||
|
@ -349,6 +366,127 @@ static int major_match(struct device *dev, const void *data)
|
|||
return MAJOR(dev->devt) == major;
|
||||
}
|
||||
|
||||
/*
|
||||
* Framing protocol management
|
||||
* Modules can implement STM protocol drivers and (un-)register them
|
||||
* with the STM class framework.
|
||||
*/
|
||||
static struct list_head stm_pdrv_head;
|
||||
static struct mutex stm_pdrv_mutex;
|
||||
|
||||
struct stm_pdrv_entry {
|
||||
struct list_head entry;
|
||||
const struct stm_protocol_driver *pdrv;
|
||||
const struct config_item_type *node_type;
|
||||
};
|
||||
|
||||
static const struct stm_pdrv_entry *
|
||||
__stm_lookup_protocol(const char *name)
|
||||
{
|
||||
struct stm_pdrv_entry *pe;
|
||||
|
||||
/*
|
||||
* If no name is given (NULL or ""), fall back to "p_basic".
|
||||
*/
|
||||
if (!name || !*name)
|
||||
name = "p_basic";
|
||||
|
||||
list_for_each_entry(pe, &stm_pdrv_head, entry) {
|
||||
if (!strcmp(name, pe->pdrv->name))
|
||||
return pe;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int stm_register_protocol(const struct stm_protocol_driver *pdrv)
|
||||
{
|
||||
struct stm_pdrv_entry *pe = NULL;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
mutex_lock(&stm_pdrv_mutex);
|
||||
|
||||
if (__stm_lookup_protocol(pdrv->name)) {
|
||||
ret = -EEXIST;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
pe = kzalloc(sizeof(*pe), GFP_KERNEL);
|
||||
if (!pe)
|
||||
goto unlock;
|
||||
|
||||
if (pdrv->policy_attr) {
|
||||
pe->node_type = get_policy_node_type(pdrv->policy_attr);
|
||||
if (!pe->node_type)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
list_add_tail(&pe->entry, &stm_pdrv_head);
|
||||
pe->pdrv = pdrv;
|
||||
|
||||
ret = 0;
|
||||
unlock:
|
||||
mutex_unlock(&stm_pdrv_mutex);
|
||||
|
||||
if (ret)
|
||||
kfree(pe);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(stm_register_protocol);
|
||||
|
||||
void stm_unregister_protocol(const struct stm_protocol_driver *pdrv)
|
||||
{
|
||||
struct stm_pdrv_entry *pe, *iter;
|
||||
|
||||
mutex_lock(&stm_pdrv_mutex);
|
||||
|
||||
list_for_each_entry_safe(pe, iter, &stm_pdrv_head, entry) {
|
||||
if (pe->pdrv == pdrv) {
|
||||
list_del(&pe->entry);
|
||||
|
||||
if (pe->node_type) {
|
||||
kfree(pe->node_type->ct_attrs);
|
||||
kfree(pe->node_type);
|
||||
}
|
||||
kfree(pe);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&stm_pdrv_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(stm_unregister_protocol);
|
||||
|
||||
static bool stm_get_protocol(const struct stm_protocol_driver *pdrv)
|
||||
{
|
||||
return try_module_get(pdrv->owner);
|
||||
}
|
||||
|
||||
void stm_put_protocol(const struct stm_protocol_driver *pdrv)
|
||||
{
|
||||
module_put(pdrv->owner);
|
||||
}
|
||||
|
||||
int stm_lookup_protocol(const char *name,
|
||||
const struct stm_protocol_driver **pdrv,
|
||||
const struct config_item_type **node_type)
|
||||
{
|
||||
const struct stm_pdrv_entry *pe;
|
||||
|
||||
mutex_lock(&stm_pdrv_mutex);
|
||||
|
||||
pe = __stm_lookup_protocol(name);
|
||||
if (pe && pe->pdrv && stm_get_protocol(pe->pdrv)) {
|
||||
*pdrv = pe->pdrv;
|
||||
*node_type = pe->node_type;
|
||||
}
|
||||
|
||||
mutex_unlock(&stm_pdrv_mutex);
|
||||
|
||||
return pe ? 0 : -ENOENT;
|
||||
}
|
||||
|
||||
static int stm_char_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct stm_file *stmf;
|
||||
|
@ -405,42 +543,81 @@ static int stm_char_release(struct inode *inode, struct file *file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int stm_file_assign(struct stm_file *stmf, char *id, unsigned int width)
|
||||
static int
|
||||
stm_assign_first_policy(struct stm_device *stm, struct stm_output *output,
|
||||
char **ids, unsigned int width)
|
||||
{
|
||||
struct stm_device *stm = stmf->stm;
|
||||
int ret;
|
||||
struct stp_policy_node *pn;
|
||||
int err, n;
|
||||
|
||||
stmf->policy_node = stp_policy_node_lookup(stm, id);
|
||||
/*
|
||||
* On success, stp_policy_node_lookup() will return holding the
|
||||
* configfs subsystem mutex, which is then released in
|
||||
* stp_policy_node_put(). This allows the pdrv->output_open() in
|
||||
* stm_output_assign() to serialize against the attribute accessors.
|
||||
*/
|
||||
for (n = 0, pn = NULL; ids[n] && !pn; n++)
|
||||
pn = stp_policy_node_lookup(stm, ids[n]);
|
||||
|
||||
ret = stm_output_assign(stm, width, stmf->policy_node, &stmf->output);
|
||||
if (!pn)
|
||||
return -EINVAL;
|
||||
|
||||
if (stmf->policy_node)
|
||||
stp_policy_node_put(stmf->policy_node);
|
||||
err = stm_output_assign(stm, width, pn, output);
|
||||
|
||||
return ret;
|
||||
stp_policy_node_put(pn);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t notrace stm_write(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel, const char *buf, size_t count)
|
||||
/**
|
||||
* stm_data_write() - send the given payload as data packets
|
||||
* @data: stm driver's data
|
||||
* @m: STP master
|
||||
* @c: STP channel
|
||||
* @ts_first: timestamp the first packet
|
||||
* @buf: data payload buffer
|
||||
* @count: data payload size
|
||||
*/
|
||||
ssize_t notrace stm_data_write(struct stm_data *data, unsigned int m,
|
||||
unsigned int c, bool ts_first, const void *buf,
|
||||
size_t count)
|
||||
{
|
||||
unsigned int flags = STP_PACKET_TIMESTAMPED;
|
||||
const unsigned char *p = buf, nil = 0;
|
||||
size_t pos;
|
||||
unsigned int flags = ts_first ? STP_PACKET_TIMESTAMPED : 0;
|
||||
ssize_t sz;
|
||||
size_t pos;
|
||||
|
||||
for (pos = 0, p = buf; count > pos; pos += sz, p += sz) {
|
||||
for (pos = 0, sz = 0; pos < count; pos += sz) {
|
||||
sz = min_t(unsigned int, count - pos, 8);
|
||||
sz = data->packet(data, master, channel, STP_PACKET_DATA, flags,
|
||||
sz, p);
|
||||
flags = 0;
|
||||
|
||||
if (sz < 0)
|
||||
sz = data->packet(data, m, c, STP_PACKET_DATA, flags, sz,
|
||||
&((u8 *)buf)[pos]);
|
||||
if (sz <= 0)
|
||||
break;
|
||||
|
||||
if (ts_first) {
|
||||
flags = 0;
|
||||
ts_first = false;
|
||||
}
|
||||
}
|
||||
|
||||
data->packet(data, master, channel, STP_PACKET_FLAG, 0, 0, &nil);
|
||||
return sz < 0 ? sz : pos;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(stm_data_write);
|
||||
|
||||
return pos;
|
||||
static ssize_t notrace
|
||||
stm_write(struct stm_device *stm, struct stm_output *output,
|
||||
unsigned int chan, const char *buf, size_t count)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* stm->pdrv is serialized against policy_mutex */
|
||||
if (!stm->pdrv)
|
||||
return -ENODEV;
|
||||
|
||||
err = stm->pdrv->write(stm->data, output, chan, buf, count);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
||||
|
@ -455,16 +632,21 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
|||
count = PAGE_SIZE - 1;
|
||||
|
||||
/*
|
||||
* if no m/c have been assigned to this writer up to this
|
||||
* point, use "default" policy entry
|
||||
* If no m/c have been assigned to this writer up to this
|
||||
* point, try to use the task name and "default" policy entries.
|
||||
*/
|
||||
if (!stmf->output.nr_chans) {
|
||||
err = stm_file_assign(stmf, "default", 1);
|
||||
char comm[sizeof(current->comm)];
|
||||
char *ids[] = { comm, "default", NULL };
|
||||
|
||||
get_task_comm(comm, current);
|
||||
|
||||
err = stm_assign_first_policy(stmf->stm, &stmf->output, ids, 1);
|
||||
/*
|
||||
* EBUSY means that somebody else just assigned this
|
||||
* output, which is just fine for write()
|
||||
*/
|
||||
if (err && err != -EBUSY)
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -480,8 +662,7 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
|||
|
||||
pm_runtime_get_sync(&stm->dev);
|
||||
|
||||
count = stm_write(stm->data, stmf->output.master, stmf->output.channel,
|
||||
kbuf, count);
|
||||
count = stm_write(stm, &stmf->output, 0, kbuf, count);
|
||||
|
||||
pm_runtime_mark_last_busy(&stm->dev);
|
||||
pm_runtime_put_autosuspend(&stm->dev);
|
||||
|
@ -550,6 +731,7 @@ static int stm_char_policy_set_ioctl(struct stm_file *stmf, void __user *arg)
|
|||
{
|
||||
struct stm_device *stm = stmf->stm;
|
||||
struct stp_policy_id *id;
|
||||
char *ids[] = { NULL, NULL };
|
||||
int ret = -EINVAL;
|
||||
u32 size;
|
||||
|
||||
|
@ -582,7 +764,9 @@ static int stm_char_policy_set_ioctl(struct stm_file *stmf, void __user *arg)
|
|||
id->width > PAGE_SIZE / stm->data->sw_mmiosz)
|
||||
goto err_free;
|
||||
|
||||
ret = stm_file_assign(stmf, id->id, id->width);
|
||||
ids[0] = id->id;
|
||||
ret = stm_assign_first_policy(stmf->stm, &stmf->output, ids,
|
||||
id->width);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
|
@ -818,8 +1002,8 @@ EXPORT_SYMBOL_GPL(stm_unregister_device);
|
|||
static int stm_source_link_add(struct stm_source_device *src,
|
||||
struct stm_device *stm)
|
||||
{
|
||||
char *id;
|
||||
int err;
|
||||
char *ids[] = { NULL, "default", NULL };
|
||||
int err = -ENOMEM;
|
||||
|
||||
mutex_lock(&stm->link_mutex);
|
||||
spin_lock(&stm->link_lock);
|
||||
|
@ -833,19 +1017,13 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
id = kstrdup(src->data->name, GFP_KERNEL);
|
||||
if (id) {
|
||||
src->policy_node =
|
||||
stp_policy_node_lookup(stm, id);
|
||||
ids[0] = kstrdup(src->data->name, GFP_KERNEL);
|
||||
if (!ids[0])
|
||||
goto fail_detach;
|
||||
|
||||
kfree(id);
|
||||
}
|
||||
|
||||
err = stm_output_assign(stm, src->data->nr_chans,
|
||||
src->policy_node, &src->output);
|
||||
|
||||
if (src->policy_node)
|
||||
stp_policy_node_put(src->policy_node);
|
||||
err = stm_assign_first_policy(stm, &src->output, ids,
|
||||
src->data->nr_chans);
|
||||
kfree(ids[0]);
|
||||
|
||||
if (err)
|
||||
goto fail_detach;
|
||||
|
@ -1134,9 +1312,7 @@ int notrace stm_source_write(struct stm_source_data *data,
|
|||
|
||||
stm = srcu_dereference(src->link, &stm_source_srcu);
|
||||
if (stm)
|
||||
count = stm_write(stm->data, src->output.master,
|
||||
src->output.channel + chan,
|
||||
buf, count);
|
||||
count = stm_write(stm, &src->output, chan, buf, count);
|
||||
else
|
||||
count = -ENODEV;
|
||||
|
||||
|
@ -1163,7 +1339,15 @@ static int __init stm_core_init(void)
|
|||
goto err_src;
|
||||
|
||||
init_srcu_struct(&stm_source_srcu);
|
||||
INIT_LIST_HEAD(&stm_pdrv_head);
|
||||
mutex_init(&stm_pdrv_mutex);
|
||||
|
||||
/*
|
||||
* So as to not confuse existing users with a requirement
|
||||
* to load yet another module, do it here.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_STM_PROTO_BASIC))
|
||||
(void)request_module_nowait("stm_p_basic");
|
||||
stm_core_up++;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -76,7 +76,7 @@ static int stm_heartbeat_init(void)
|
|||
goto fail_unregister;
|
||||
|
||||
stm_heartbeat[i].data.nr_chans = 1;
|
||||
stm_heartbeat[i].data.link = stm_heartbeat_link;
|
||||
stm_heartbeat[i].data.link = stm_heartbeat_link;
|
||||
stm_heartbeat[i].data.unlink = stm_heartbeat_unlink;
|
||||
hrtimer_init(&stm_heartbeat[i].hrtimer, CLOCK_MONOTONIC,
|
||||
HRTIMER_MODE_ABS);
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Basic framing protocol for STM devices.
|
||||
* Copyright (c) 2018, Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/stm.h>
|
||||
#include "stm.h"
|
||||
|
||||
static ssize_t basic_write(struct stm_data *data, struct stm_output *output,
|
||||
unsigned int chan, const char *buf, size_t count)
|
||||
{
|
||||
unsigned int c = output->channel + chan;
|
||||
unsigned int m = output->master;
|
||||
const unsigned char nil = 0;
|
||||
ssize_t sz;
|
||||
|
||||
sz = stm_data_write(data, m, c, true, buf, count);
|
||||
if (sz > 0)
|
||||
data->packet(data, m, c, STP_PACKET_FLAG, 0, 0, &nil);
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
static const struct stm_protocol_driver basic_pdrv = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "p_basic",
|
||||
.write = basic_write,
|
||||
};
|
||||
|
||||
static int basic_stm_init(void)
|
||||
{
|
||||
return stm_register_protocol(&basic_pdrv);
|
||||
}
|
||||
|
||||
static void basic_stm_exit(void)
|
||||
{
|
||||
stm_unregister_protocol(&basic_pdrv);
|
||||
}
|
||||
|
||||
module_init(basic_stm_init);
|
||||
module_exit(basic_stm_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Basic STM framing protocol driver");
|
||||
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");
|
|
@ -0,0 +1,382 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MIPI SyS-T framing protocol for STM devices.
|
||||
* Copyright (c) 2018, Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/configfs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uuid.h>
|
||||
#include <linux/stm.h>
|
||||
#include "stm.h"
|
||||
|
||||
enum sys_t_message_type {
|
||||
MIPI_SYST_TYPE_BUILD = 0,
|
||||
MIPI_SYST_TYPE_SHORT32,
|
||||
MIPI_SYST_TYPE_STRING,
|
||||
MIPI_SYST_TYPE_CATALOG,
|
||||
MIPI_SYST_TYPE_RAW = 6,
|
||||
MIPI_SYST_TYPE_SHORT64,
|
||||
MIPI_SYST_TYPE_CLOCK,
|
||||
};
|
||||
|
||||
enum sys_t_message_severity {
|
||||
MIPI_SYST_SEVERITY_MAX = 0,
|
||||
MIPI_SYST_SEVERITY_FATAL,
|
||||
MIPI_SYST_SEVERITY_ERROR,
|
||||
MIPI_SYST_SEVERITY_WARNING,
|
||||
MIPI_SYST_SEVERITY_INFO,
|
||||
MIPI_SYST_SEVERITY_USER1,
|
||||
MIPI_SYST_SEVERITY_USER2,
|
||||
MIPI_SYST_SEVERITY_DEBUG,
|
||||
};
|
||||
|
||||
enum sys_t_message_build_subtype {
|
||||
MIPI_SYST_BUILD_ID_COMPACT32 = 0,
|
||||
MIPI_SYST_BUILD_ID_COMPACT64,
|
||||
MIPI_SYST_BUILD_ID_LONG,
|
||||
};
|
||||
|
||||
enum sys_t_message_clock_subtype {
|
||||
MIPI_SYST_CLOCK_TRANSPORT_SYNC = 1,
|
||||
};
|
||||
|
||||
enum sys_t_message_string_subtype {
|
||||
MIPI_SYST_STRING_GENERIC = 1,
|
||||
MIPI_SYST_STRING_FUNCTIONENTER,
|
||||
MIPI_SYST_STRING_FUNCTIONEXIT,
|
||||
MIPI_SYST_STRING_INVALIDPARAM = 5,
|
||||
MIPI_SYST_STRING_ASSERT = 7,
|
||||
MIPI_SYST_STRING_PRINTF_32 = 11,
|
||||
MIPI_SYST_STRING_PRINTF_64 = 12,
|
||||
};
|
||||
|
||||
#define MIPI_SYST_TYPE(t) ((u32)(MIPI_SYST_TYPE_ ## t))
|
||||
#define MIPI_SYST_SEVERITY(s) ((u32)(MIPI_SYST_SEVERITY_ ## s) << 4)
|
||||
#define MIPI_SYST_OPT_LOC BIT(8)
|
||||
#define MIPI_SYST_OPT_LEN BIT(9)
|
||||
#define MIPI_SYST_OPT_CHK BIT(10)
|
||||
#define MIPI_SYST_OPT_TS BIT(11)
|
||||
#define MIPI_SYST_UNIT(u) ((u32)(u) << 12)
|
||||
#define MIPI_SYST_ORIGIN(o) ((u32)(o) << 16)
|
||||
#define MIPI_SYST_OPT_GUID BIT(23)
|
||||
#define MIPI_SYST_SUBTYPE(s) ((u32)(MIPI_SYST_ ## s) << 24)
|
||||
#define MIPI_SYST_UNITLARGE(u) (MIPI_SYST_UNIT(u & 0xf) | \
|
||||
MIPI_SYST_ORIGIN(u >> 4))
|
||||
#define MIPI_SYST_TYPES(t, s) (MIPI_SYST_TYPE(t) | \
|
||||
MIPI_SYST_SUBTYPE(t ## _ ## s))
|
||||
|
||||
#define DATA_HEADER (MIPI_SYST_TYPES(STRING, GENERIC) | \
|
||||
MIPI_SYST_SEVERITY(INFO) | \
|
||||
MIPI_SYST_OPT_GUID)
|
||||
|
||||
#define CLOCK_SYNC_HEADER (MIPI_SYST_TYPES(CLOCK, TRANSPORT_SYNC) | \
|
||||
MIPI_SYST_SEVERITY(MAX))
|
||||
|
||||
struct sys_t_policy_node {
|
||||
uuid_t uuid;
|
||||
bool do_len;
|
||||
unsigned long ts_interval;
|
||||
unsigned long clocksync_interval;
|
||||
};
|
||||
|
||||
struct sys_t_output {
|
||||
struct sys_t_policy_node node;
|
||||
unsigned long ts_jiffies;
|
||||
unsigned long clocksync_jiffies;
|
||||
};
|
||||
|
||||
static void sys_t_policy_node_init(void *priv)
|
||||
{
|
||||
struct sys_t_policy_node *pn = priv;
|
||||
|
||||
generate_random_uuid(pn->uuid.b);
|
||||
}
|
||||
|
||||
static int sys_t_output_open(void *priv, struct stm_output *output)
|
||||
{
|
||||
struct sys_t_policy_node *pn = priv;
|
||||
struct sys_t_output *opriv;
|
||||
|
||||
opriv = kzalloc(sizeof(*opriv), GFP_ATOMIC);
|
||||
if (!opriv)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&opriv->node, pn, sizeof(opriv->node));
|
||||
output->pdrv_private = opriv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sys_t_output_close(struct stm_output *output)
|
||||
{
|
||||
kfree(output->pdrv_private);
|
||||
}
|
||||
|
||||
static ssize_t sys_t_policy_uuid_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
|
||||
return sprintf(page, "%pU\n", &pn->uuid);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sys_t_policy_uuid_store(struct config_item *item, const char *page,
|
||||
size_t count)
|
||||
{
|
||||
struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
int ret;
|
||||
|
||||
mutex_lock(mutexp);
|
||||
ret = uuid_parse(page, &pn->uuid);
|
||||
mutex_unlock(mutexp);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(sys_t_policy_, uuid);
|
||||
|
||||
static ssize_t sys_t_policy_do_len_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
|
||||
return sprintf(page, "%d\n", pn->do_len);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sys_t_policy_do_len_store(struct config_item *item, const char *page,
|
||||
size_t count)
|
||||
{
|
||||
struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
int ret;
|
||||
|
||||
mutex_lock(mutexp);
|
||||
ret = kstrtobool(page, &pn->do_len);
|
||||
mutex_unlock(mutexp);
|
||||
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(sys_t_policy_, do_len);
|
||||
|
||||
static ssize_t sys_t_policy_ts_interval_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
|
||||
return sprintf(page, "%u\n", jiffies_to_msecs(pn->ts_interval));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sys_t_policy_ts_interval_store(struct config_item *item, const char *page,
|
||||
size_t count)
|
||||
{
|
||||
struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
unsigned int ms;
|
||||
int ret;
|
||||
|
||||
mutex_lock(mutexp);
|
||||
ret = kstrtouint(page, 10, &ms);
|
||||
mutex_unlock(mutexp);
|
||||
|
||||
if (!ret) {
|
||||
pn->ts_interval = msecs_to_jiffies(ms);
|
||||
return count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(sys_t_policy_, ts_interval);
|
||||
|
||||
static ssize_t sys_t_policy_clocksync_interval_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
|
||||
return sprintf(page, "%u\n", jiffies_to_msecs(pn->clocksync_interval));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sys_t_policy_clocksync_interval_store(struct config_item *item,
|
||||
const char *page, size_t count)
|
||||
{
|
||||
struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
|
||||
struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
|
||||
unsigned int ms;
|
||||
int ret;
|
||||
|
||||
mutex_lock(mutexp);
|
||||
ret = kstrtouint(page, 10, &ms);
|
||||
mutex_unlock(mutexp);
|
||||
|
||||
if (!ret) {
|
||||
pn->clocksync_interval = msecs_to_jiffies(ms);
|
||||
return count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(sys_t_policy_, clocksync_interval);
|
||||
|
||||
static struct configfs_attribute *sys_t_policy_attrs[] = {
|
||||
&sys_t_policy_attr_uuid,
|
||||
&sys_t_policy_attr_do_len,
|
||||
&sys_t_policy_attr_ts_interval,
|
||||
&sys_t_policy_attr_clocksync_interval,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static inline bool sys_t_need_ts(struct sys_t_output *op)
|
||||
{
|
||||
if (op->node.ts_interval &&
|
||||
time_after(op->ts_jiffies + op->node.ts_interval, jiffies)) {
|
||||
op->ts_jiffies = jiffies;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sys_t_need_clock_sync(struct sys_t_output *op)
|
||||
{
|
||||
if (op->node.clocksync_interval &&
|
||||
time_after(op->clocksync_jiffies + op->node.clocksync_interval,
|
||||
jiffies)) {
|
||||
op->clocksync_jiffies = jiffies;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sys_t_clock_sync(struct stm_data *data, unsigned int m, unsigned int c)
|
||||
{
|
||||
u32 header = CLOCK_SYNC_HEADER;
|
||||
const unsigned char nil = 0;
|
||||
u64 payload[2]; /* Clock value and frequency */
|
||||
ssize_t sz;
|
||||
|
||||
sz = data->packet(data, m, c, STP_PACKET_DATA, STP_PACKET_TIMESTAMPED,
|
||||
4, (u8 *)&header);
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
|
||||
payload[0] = ktime_get_real_ns();
|
||||
payload[1] = NSEC_PER_SEC;
|
||||
sz = stm_data_write(data, m, c, false, &payload, sizeof(payload));
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
|
||||
data->packet(data, m, c, STP_PACKET_FLAG, 0, 0, &nil);
|
||||
|
||||
return sizeof(header) + sizeof(payload);
|
||||
}
|
||||
|
||||
static ssize_t sys_t_write(struct stm_data *data, struct stm_output *output,
|
||||
unsigned int chan, const char *buf, size_t count)
|
||||
{
|
||||
struct sys_t_output *op = output->pdrv_private;
|
||||
unsigned int c = output->channel + chan;
|
||||
unsigned int m = output->master;
|
||||
const unsigned char nil = 0;
|
||||
u32 header = DATA_HEADER;
|
||||
ssize_t sz;
|
||||
|
||||
/* We require an existing policy node to proceed */
|
||||
if (!op)
|
||||
return -EINVAL;
|
||||
|
||||
if (sys_t_need_clock_sync(op)) {
|
||||
sz = sys_t_clock_sync(data, m, c);
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
}
|
||||
|
||||
if (op->node.do_len)
|
||||
header |= MIPI_SYST_OPT_LEN;
|
||||
if (sys_t_need_ts(op))
|
||||
header |= MIPI_SYST_OPT_TS;
|
||||
|
||||
/*
|
||||
* STP framing rules for SyS-T frames:
|
||||
* * the first packet of the SyS-T frame is timestamped;
|
||||
* * the last packet is a FLAG.
|
||||
*/
|
||||
/* Message layout: HEADER / GUID / [LENGTH /][TIMESTAMP /] DATA */
|
||||
/* HEADER */
|
||||
sz = data->packet(data, m, c, STP_PACKET_DATA, STP_PACKET_TIMESTAMPED,
|
||||
4, (u8 *)&header);
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
|
||||
/* GUID */
|
||||
sz = stm_data_write(data, m, c, false, op->node.uuid.b, UUID_SIZE);
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
|
||||
/* [LENGTH] */
|
||||
if (op->node.do_len) {
|
||||
u16 length = count;
|
||||
|
||||
sz = data->packet(data, m, c, STP_PACKET_DATA, 0, 2,
|
||||
(u8 *)&length);
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
}
|
||||
|
||||
/* [TIMESTAMP] */
|
||||
if (header & MIPI_SYST_OPT_TS) {
|
||||
u64 ts = ktime_get_real_ns();
|
||||
|
||||
sz = stm_data_write(data, m, c, false, &ts, sizeof(ts));
|
||||
if (sz <= 0)
|
||||
return sz;
|
||||
}
|
||||
|
||||
/* DATA */
|
||||
sz = stm_data_write(data, m, c, false, buf, count);
|
||||
if (sz > 0)
|
||||
data->packet(data, m, c, STP_PACKET_FLAG, 0, 0, &nil);
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
static const struct stm_protocol_driver sys_t_pdrv = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "p_sys-t",
|
||||
.priv_sz = sizeof(struct sys_t_policy_node),
|
||||
.write = sys_t_write,
|
||||
.policy_attr = sys_t_policy_attrs,
|
||||
.policy_node_init = sys_t_policy_node_init,
|
||||
.output_open = sys_t_output_open,
|
||||
.output_close = sys_t_output_close,
|
||||
};
|
||||
|
||||
static int sys_t_stm_init(void)
|
||||
{
|
||||
return stm_register_protocol(&sys_t_pdrv);
|
||||
}
|
||||
|
||||
static void sys_t_stm_exit(void)
|
||||
{
|
||||
stm_unregister_protocol(&sys_t_pdrv);
|
||||
}
|
||||
|
||||
module_init(sys_t_stm_init);
|
||||
module_exit(sys_t_stm_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("MIPI SyS-T STM framing protocol driver");
|
||||
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");
|
|
@ -33,8 +33,18 @@ struct stp_policy_node {
|
|||
unsigned int last_master;
|
||||
unsigned int first_channel;
|
||||
unsigned int last_channel;
|
||||
/* this is the one that's exposed to the attributes */
|
||||
unsigned char priv[0];
|
||||
};
|
||||
|
||||
void *stp_policy_node_priv(struct stp_policy_node *pn)
|
||||
{
|
||||
if (!pn)
|
||||
return NULL;
|
||||
|
||||
return pn->priv;
|
||||
}
|
||||
|
||||
static struct configfs_subsystem stp_policy_subsys;
|
||||
|
||||
void stp_policy_node_get_ranges(struct stp_policy_node *policy_node,
|
||||
|
@ -68,6 +78,14 @@ to_stp_policy_node(struct config_item *item)
|
|||
NULL;
|
||||
}
|
||||
|
||||
void *to_pdrv_policy_node(struct config_item *item)
|
||||
{
|
||||
struct stp_policy_node *node = to_stp_policy_node(item);
|
||||
|
||||
return stp_policy_node_priv(node);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(to_pdrv_policy_node);
|
||||
|
||||
static ssize_t
|
||||
stp_policy_node_masters_show(struct config_item *item, char *page)
|
||||
{
|
||||
|
@ -163,7 +181,9 @@ unlock:
|
|||
|
||||
static void stp_policy_node_release(struct config_item *item)
|
||||
{
|
||||
kfree(to_stp_policy_node(item));
|
||||
struct stp_policy_node *node = to_stp_policy_node(item);
|
||||
|
||||
kfree(node);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations stp_policy_node_item_ops = {
|
||||
|
@ -182,10 +202,34 @@ static struct configfs_attribute *stp_policy_node_attrs[] = {
|
|||
static const struct config_item_type stp_policy_type;
|
||||
static const struct config_item_type stp_policy_node_type;
|
||||
|
||||
const struct config_item_type *
|
||||
get_policy_node_type(struct configfs_attribute **attrs)
|
||||
{
|
||||
struct config_item_type *type;
|
||||
struct configfs_attribute **merged;
|
||||
|
||||
type = kmemdup(&stp_policy_node_type, sizeof(stp_policy_node_type),
|
||||
GFP_KERNEL);
|
||||
if (!type)
|
||||
return NULL;
|
||||
|
||||
merged = memcat_p(stp_policy_node_attrs, attrs);
|
||||
if (!merged) {
|
||||
kfree(type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
type->ct_attrs = merged;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
static struct config_group *
|
||||
stp_policy_node_make(struct config_group *group, const char *name)
|
||||
{
|
||||
const struct config_item_type *type = &stp_policy_node_type;
|
||||
struct stp_policy_node *policy_node, *parent_node;
|
||||
const struct stm_protocol_driver *pdrv;
|
||||
struct stp_policy *policy;
|
||||
|
||||
if (group->cg_item.ci_type == &stp_policy_type) {
|
||||
|
@ -199,12 +243,20 @@ stp_policy_node_make(struct config_group *group, const char *name)
|
|||
if (!policy->stm)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
policy_node = kzalloc(sizeof(struct stp_policy_node), GFP_KERNEL);
|
||||
pdrv = policy->stm->pdrv;
|
||||
policy_node =
|
||||
kzalloc(offsetof(struct stp_policy_node, priv[pdrv->priv_sz]),
|
||||
GFP_KERNEL);
|
||||
if (!policy_node)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
config_group_init_type_name(&policy_node->group, name,
|
||||
&stp_policy_node_type);
|
||||
if (pdrv->policy_node_init)
|
||||
pdrv->policy_node_init((void *)policy_node->priv);
|
||||
|
||||
if (policy->stm->pdrv_node_type)
|
||||
type = policy->stm->pdrv_node_type;
|
||||
|
||||
config_group_init_type_name(&policy_node->group, name, type);
|
||||
|
||||
policy_node->policy = policy;
|
||||
|
||||
|
@ -254,8 +306,25 @@ static ssize_t stp_policy_device_show(struct config_item *item,
|
|||
|
||||
CONFIGFS_ATTR_RO(stp_policy_, device);
|
||||
|
||||
static ssize_t stp_policy_protocol_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct stp_policy *policy = to_stp_policy(item);
|
||||
ssize_t count;
|
||||
|
||||
count = sprintf(page, "%s\n",
|
||||
(policy && policy->stm) ?
|
||||
policy->stm->pdrv->name :
|
||||
"<none>");
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_RO(stp_policy_, protocol);
|
||||
|
||||
static struct configfs_attribute *stp_policy_attrs[] = {
|
||||
&stp_policy_attr_device,
|
||||
&stp_policy_attr_protocol,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -276,6 +345,7 @@ void stp_policy_unbind(struct stp_policy *policy)
|
|||
stm->policy = NULL;
|
||||
policy->stm = NULL;
|
||||
|
||||
stm_put_protocol(stm->pdrv);
|
||||
stm_put_device(stm);
|
||||
}
|
||||
|
||||
|
@ -311,11 +381,14 @@ static const struct config_item_type stp_policy_type = {
|
|||
};
|
||||
|
||||
static struct config_group *
|
||||
stp_policies_make(struct config_group *group, const char *name)
|
||||
stp_policy_make(struct config_group *group, const char *name)
|
||||
{
|
||||
const struct config_item_type *pdrv_node_type;
|
||||
const struct stm_protocol_driver *pdrv;
|
||||
char *devname, *proto, *p;
|
||||
struct config_group *ret;
|
||||
struct stm_device *stm;
|
||||
char *devname, *p;
|
||||
int err;
|
||||
|
||||
devname = kasprintf(GFP_KERNEL, "%s", name);
|
||||
if (!devname)
|
||||
|
@ -326,6 +399,7 @@ stp_policies_make(struct config_group *group, const char *name)
|
|||
* <device_name> is the name of an existing stm device; may
|
||||
* contain dots;
|
||||
* <policy_name> is an arbitrary string; may not contain dots
|
||||
* <device_name>:<protocol_name>.<policy_name>
|
||||
*/
|
||||
p = strrchr(devname, '.');
|
||||
if (!p) {
|
||||
|
@ -335,11 +409,28 @@ stp_policies_make(struct config_group *group, const char *name)
|
|||
|
||||
*p = '\0';
|
||||
|
||||
/*
|
||||
* look for ":<protocol_name>":
|
||||
* + no protocol suffix: fall back to whatever is available;
|
||||
* + unknown protocol: fail the whole thing
|
||||
*/
|
||||
proto = strrchr(devname, ':');
|
||||
if (proto)
|
||||
*proto++ = '\0';
|
||||
|
||||
stm = stm_find_device(devname);
|
||||
if (!stm) {
|
||||
kfree(devname);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
err = stm_lookup_protocol(proto, &pdrv, &pdrv_node_type);
|
||||
kfree(devname);
|
||||
|
||||
if (!stm)
|
||||
if (err) {
|
||||
stm_put_device(stm);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
mutex_lock(&stm->policy_mutex);
|
||||
if (stm->policy) {
|
||||
|
@ -349,31 +440,37 @@ stp_policies_make(struct config_group *group, const char *name)
|
|||
|
||||
stm->policy = kzalloc(sizeof(*stm->policy), GFP_KERNEL);
|
||||
if (!stm->policy) {
|
||||
ret = ERR_PTR(-ENOMEM);
|
||||
goto unlock_policy;
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
stm_put_protocol(pdrv);
|
||||
stm_put_device(stm);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
config_group_init_type_name(&stm->policy->group, name,
|
||||
&stp_policy_type);
|
||||
stm->policy->stm = stm;
|
||||
|
||||
stm->pdrv = pdrv;
|
||||
stm->pdrv_node_type = pdrv_node_type;
|
||||
stm->policy->stm = stm;
|
||||
ret = &stm->policy->group;
|
||||
|
||||
unlock_policy:
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
|
||||
if (IS_ERR(ret))
|
||||
if (IS_ERR(ret)) {
|
||||
stm_put_protocol(stm->pdrv);
|
||||
stm_put_device(stm);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct configfs_group_operations stp_policies_group_ops = {
|
||||
.make_group = stp_policies_make,
|
||||
static struct configfs_group_operations stp_policy_root_group_ops = {
|
||||
.make_group = stp_policy_make,
|
||||
};
|
||||
|
||||
static const struct config_item_type stp_policies_type = {
|
||||
.ct_group_ops = &stp_policies_group_ops,
|
||||
static const struct config_item_type stp_policy_root_type = {
|
||||
.ct_group_ops = &stp_policy_root_group_ops,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
@ -381,7 +478,7 @@ static struct configfs_subsystem stp_policy_subsys = {
|
|||
.su_group = {
|
||||
.cg_item = {
|
||||
.ci_namebuf = "stp-policy",
|
||||
.ci_type = &stp_policies_type,
|
||||
.ci_type = &stp_policy_root_type,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -392,7 +489,7 @@ static struct configfs_subsystem stp_policy_subsys = {
|
|||
static struct stp_policy_node *
|
||||
__stp_policy_node_lookup(struct stp_policy *policy, char *s)
|
||||
{
|
||||
struct stp_policy_node *policy_node, *ret;
|
||||
struct stp_policy_node *policy_node, *ret = NULL;
|
||||
struct list_head *head = &policy->group.cg_children;
|
||||
struct config_item *item;
|
||||
char *start, *end = s;
|
||||
|
@ -400,10 +497,6 @@ __stp_policy_node_lookup(struct stp_policy *policy, char *s)
|
|||
if (list_empty(head))
|
||||
return NULL;
|
||||
|
||||
/* return the first entry if everything else fails */
|
||||
item = list_entry(head->next, struct config_item, ci_entry);
|
||||
ret = to_stp_policy_node(item);
|
||||
|
||||
next:
|
||||
for (;;) {
|
||||
start = strsep(&end, "/");
|
||||
|
@ -449,25 +542,25 @@ stp_policy_node_lookup(struct stm_device *stm, char *s)
|
|||
|
||||
if (policy_node)
|
||||
config_item_get(&policy_node->group.cg_item);
|
||||
mutex_unlock(&stp_policy_subsys.su_mutex);
|
||||
else
|
||||
mutex_unlock(&stp_policy_subsys.su_mutex);
|
||||
|
||||
return policy_node;
|
||||
}
|
||||
|
||||
void stp_policy_node_put(struct stp_policy_node *policy_node)
|
||||
{
|
||||
lockdep_assert_held(&stp_policy_subsys.su_mutex);
|
||||
|
||||
mutex_unlock(&stp_policy_subsys.su_mutex);
|
||||
config_item_put(&policy_node->group.cg_item);
|
||||
}
|
||||
|
||||
int __init stp_configfs_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
config_group_init(&stp_policy_subsys.su_group);
|
||||
mutex_init(&stp_policy_subsys.su_mutex);
|
||||
err = configfs_register_subsystem(&stp_policy_subsys);
|
||||
|
||||
return err;
|
||||
return configfs_register_subsystem(&stp_policy_subsys);
|
||||
}
|
||||
|
||||
void __exit stp_configfs_exit(void)
|
||||
|
|
|
@ -10,20 +10,17 @@
|
|||
#ifndef _STM_STM_H_
|
||||
#define _STM_STM_H_
|
||||
|
||||
#include <linux/configfs.h>
|
||||
|
||||
struct stp_policy;
|
||||
struct stp_policy_node;
|
||||
struct stm_protocol_driver;
|
||||
|
||||
struct stp_policy_node *
|
||||
stp_policy_node_lookup(struct stm_device *stm, char *s);
|
||||
void stp_policy_node_put(struct stp_policy_node *policy_node);
|
||||
void stp_policy_unbind(struct stp_policy *policy);
|
||||
|
||||
void stp_policy_node_get_ranges(struct stp_policy_node *policy_node,
|
||||
unsigned int *mstart, unsigned int *mend,
|
||||
unsigned int *cstart, unsigned int *cend);
|
||||
int stp_configfs_init(void);
|
||||
void stp_configfs_exit(void);
|
||||
|
||||
void *stp_policy_node_priv(struct stp_policy_node *pn);
|
||||
|
||||
struct stp_master {
|
||||
unsigned int nr_free;
|
||||
unsigned long chan_map[0];
|
||||
|
@ -40,6 +37,9 @@ struct stm_device {
|
|||
struct mutex link_mutex;
|
||||
spinlock_t link_lock;
|
||||
struct list_head link_list;
|
||||
/* framing protocol in use */
|
||||
const struct stm_protocol_driver *pdrv;
|
||||
const struct config_item_type *pdrv_node_type;
|
||||
/* master allocation */
|
||||
spinlock_t mc_lock;
|
||||
struct stp_master *masters[0];
|
||||
|
@ -48,16 +48,28 @@ struct stm_device {
|
|||
#define to_stm_device(_d) \
|
||||
container_of((_d), struct stm_device, dev)
|
||||
|
||||
struct stp_policy_node *
|
||||
stp_policy_node_lookup(struct stm_device *stm, char *s);
|
||||
void stp_policy_node_put(struct stp_policy_node *policy_node);
|
||||
void stp_policy_unbind(struct stp_policy *policy);
|
||||
|
||||
void stp_policy_node_get_ranges(struct stp_policy_node *policy_node,
|
||||
unsigned int *mstart, unsigned int *mend,
|
||||
unsigned int *cstart, unsigned int *cend);
|
||||
|
||||
const struct config_item_type *
|
||||
get_policy_node_type(struct configfs_attribute **attrs);
|
||||
|
||||
struct stm_output {
|
||||
spinlock_t lock;
|
||||
unsigned int master;
|
||||
unsigned int channel;
|
||||
unsigned int nr_chans;
|
||||
void *pdrv_private;
|
||||
};
|
||||
|
||||
struct stm_file {
|
||||
struct stm_device *stm;
|
||||
struct stp_policy_node *policy_node;
|
||||
struct stm_output output;
|
||||
};
|
||||
|
||||
|
@ -71,11 +83,35 @@ struct stm_source_device {
|
|||
struct stm_device __rcu *link;
|
||||
struct list_head link_entry;
|
||||
/* one output per stm_source device */
|
||||
struct stp_policy_node *policy_node;
|
||||
struct stm_output output;
|
||||
};
|
||||
|
||||
#define to_stm_source_device(_d) \
|
||||
container_of((_d), struct stm_source_device, dev)
|
||||
|
||||
void *to_pdrv_policy_node(struct config_item *item);
|
||||
|
||||
struct stm_protocol_driver {
|
||||
struct module *owner;
|
||||
const char *name;
|
||||
ssize_t (*write)(struct stm_data *data,
|
||||
struct stm_output *output, unsigned int chan,
|
||||
const char *buf, size_t count);
|
||||
void (*policy_node_init)(void *arg);
|
||||
int (*output_open)(void *priv, struct stm_output *output);
|
||||
void (*output_close)(struct stm_output *output);
|
||||
ssize_t priv_sz;
|
||||
struct configfs_attribute **policy_attr;
|
||||
};
|
||||
|
||||
int stm_register_protocol(const struct stm_protocol_driver *pdrv);
|
||||
void stm_unregister_protocol(const struct stm_protocol_driver *pdrv);
|
||||
int stm_lookup_protocol(const char *name,
|
||||
const struct stm_protocol_driver **pdrv,
|
||||
const struct config_item_type **type);
|
||||
void stm_put_protocol(const struct stm_protocol_driver *pdrv);
|
||||
ssize_t stm_data_write(struct stm_data *data, unsigned int m,
|
||||
unsigned int c, bool ts_first, const void *buf,
|
||||
size_t count);
|
||||
|
||||
#endif /* _STM_STM_H_ */
|
||||
|
|
|
@ -114,6 +114,6 @@ static struct i2c_driver ad_dpot_i2c_driver = {
|
|||
|
||||
module_i2c_driver(ad_dpot_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
|
||||
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
|
||||
MODULE_DESCRIPTION("digital potentiometer I2C bus driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -140,7 +140,7 @@ static struct spi_driver ad_dpot_spi_driver = {
|
|||
|
||||
module_spi_driver(ad_dpot_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
|
||||
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
|
||||
MODULE_DESCRIPTION("digital potentiometer SPI bus driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("spi:ad_dpot");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* ad525x_dpot: Driver for the Analog Devices digital potentiometers
|
||||
* Copyright (c) 2009-2010 Analog Devices, Inc.
|
||||
* Author: Michael Hennerich <hennerich@blackfin.uclinux.org>
|
||||
* Author: Michael Hennerich <michael.hennerich@analog.com>
|
||||
*
|
||||
* DEVID #Wipers #Positions Resistor Options (kOhm)
|
||||
* AD5258 1 64 1, 10, 50, 100
|
||||
|
@ -64,7 +64,7 @@
|
|||
* Author: Chris Verges <chrisv@cyberswitching.com>
|
||||
*
|
||||
* derived from ad5252.c
|
||||
* Copyright (c) 2006-2011 Michael Hennerich <hennerich@blackfin.uclinux.org>
|
||||
* Copyright (c) 2006-2011 Michael Hennerich <michael.hennerich@analog.com>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
@ -760,6 +760,6 @@ EXPORT_SYMBOL(ad_dpot_remove);
|
|||
|
||||
|
||||
MODULE_AUTHOR("Chris Verges <chrisv@cyberswitching.com>, "
|
||||
"Michael Hennerich <hennerich@blackfin.uclinux.org>");
|
||||
"Michael Hennerich <michael.hennerich@analog.com>");
|
||||
MODULE_DESCRIPTION("Digital potentiometer driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -188,7 +188,6 @@ struct apds990x_chip {
|
|||
#define APDS_LUX_DEFAULT_RATE 200
|
||||
|
||||
static const u8 again[] = {1, 8, 16, 120}; /* ALS gain steps */
|
||||
static const u8 ir_currents[] = {100, 50, 25, 12}; /* IRled currents in mA */
|
||||
|
||||
/* Following two tables must match i.e 10Hz rate means 1 as persistence value */
|
||||
static const u16 arates_hz[] = {10, 5, 2, 1};
|
||||
|
|
|
@ -180,9 +180,6 @@ static const char reg_vleds[] = "Vleds";
|
|||
static const s16 prox_rates_hz[] = {100, 50, 33, 25, 14, 10, 5, 2};
|
||||
static const s16 prox_rates_ms[] = {10, 20, 30, 40, 70, 100, 200, 500};
|
||||
|
||||
/* Supported IR-led currents in mA */
|
||||
static const u8 prox_curr_ma[] = {5, 10, 20, 50, 100, 150, 200};
|
||||
|
||||
/*
|
||||
* Supported stand alone rates in ms from chip data sheet
|
||||
* {100, 200, 500, 1000, 2000};
|
||||
|
|
|
@ -92,8 +92,8 @@ static int update_property(struct device_node *dn, const char *name,
|
|||
|
||||
val = (u32 *)new_prop->value;
|
||||
rc = cxl_update_properties(dn, new_prop);
|
||||
pr_devel("%s: update property (%s, length: %i, value: %#x)\n",
|
||||
dn->name, name, vd, be32_to_cpu(*val));
|
||||
pr_devel("%pOFn: update property (%s, length: %i, value: %#x)\n",
|
||||
dn, name, vd, be32_to_cpu(*val));
|
||||
|
||||
if (rc) {
|
||||
kfree(new_prop->name);
|
||||
|
|
|
@ -1018,8 +1018,6 @@ err1:
|
|||
|
||||
void cxl_guest_remove_afu(struct cxl_afu *afu)
|
||||
{
|
||||
pr_devel("in %s - AFU(%d)\n", __func__, afu->slice);
|
||||
|
||||
if (!afu)
|
||||
return;
|
||||
|
||||
|
|
|
@ -381,7 +381,7 @@ int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx)
|
|||
*/
|
||||
ec->factor = 0;
|
||||
ec->shift = 0;
|
||||
if ((ec->nonupdate_dwell == 0)) {
|
||||
if (!ec->nonupdate_dwell) {
|
||||
int p, logp, shift;
|
||||
|
||||
/* Determine:
|
||||
|
|
|
@ -111,4 +111,15 @@ config EEPROM_IDT_89HPESX
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called idt_89hpesx.
|
||||
|
||||
config EEPROM_EE1004
|
||||
tristate "SPD EEPROMs on DDR4 memory modules"
|
||||
depends on I2C && SYSFS
|
||||
help
|
||||
Enable this driver to get read support to SPD EEPROMs following
|
||||
the JEDEC EE1004 standard. These are typically found on DDR4
|
||||
SDRAM memory modules.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ee1004.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
|
|||
obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o
|
||||
obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
|
||||
obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
|
||||
obj-$(CONFIG_EEPROM_EE1004) += ee1004.o
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue