msm: smp2p: Add new SMSM Point-to-Point Driver
The Shared Memory Point to Point (SMP2P) driver facilitates communication of multiple 32-bit values between two processors. Each value (called a state entry) has a single writer (the local side) and a single reader (the remote side). Entries are uniquely identified in the system by the directed edge (local processor ID to remote processor ID) and a string identifier. This driver, which is layered on top of shared memory (SMEM), will eventually replace the existing broadcast implementation of SMSM. This driver has several advantages over SMSM including: * Point-to-point communication which allows for XPU protection of SMEM * Multiple 32-bit state values for future expansion * GPIO and Virtual interrupt support for client-facing API * Version and feature negotiation to allow for phased upgrades of a system without requiring cross-processor dependencies. Change-Id: Iefe2169ae686a3e5f400b3a7039f6b114a8b7e5a Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
This commit is contained in:
parent
792883d779
commit
4b9d5de206
|
@ -0,0 +1,416 @@
|
|||
Introduction
|
||||
============
|
||||
The Shared Memory Point to Point (SMP2P) protocol facilitates communication of
|
||||
a single 32-bit value between two processors. Each value has a single writer
|
||||
(the local side) and a single reader (the remote side). Values are uniquely
|
||||
identified in the system by the directed edge (local processor ID to remote
|
||||
processor ID) and a string identifier.
|
||||
|
||||
Version and feature negotiation has been included in the design to allow for
|
||||
phased upgrades of all processors.
|
||||
|
||||
Software Architecture Description
|
||||
=================================
|
||||
The data and interrupt coupling between processors is shown in Fig. 1. Each
|
||||
processor is responsible for creating the outgoing SMEM items and each item is
|
||||
writable by the local processor and readable by the remote processor. By using
|
||||
two separate SMEM items that are single-reader and single-writer, SMP2P does
|
||||
not require any remote locking mechanisms.
|
||||
|
||||
The client API uses the Linux GPIO and interrupt framework to expose a virtual
|
||||
GPIO and a virtual interrupt controller for each entry.
|
||||
|
||||
=================
|
||||
| |
|
||||
-----write------>|SMEM item A->B |-----read------
|
||||
| | | |
|
||||
| ================= |
|
||||
| |
|
||||
| v
|
||||
GPIO API => ------------ ======= Interrupt line ======> ------------
|
||||
Processor A Processor B
|
||||
Interrupt <= ------------ <====== Interrupt line ======= ------------
|
||||
API ^ |
|
||||
| |
|
||||
| |
|
||||
| ================= |
|
||||
| | | |
|
||||
------read-------|SMEM item A<-B |<-----write----
|
||||
| |
|
||||
=================
|
||||
|
||||
Fig 1
|
||||
|
||||
|
||||
Design
|
||||
======
|
||||
Each SMEM item contains a header that is used to identify and manage the edge
|
||||
along with an array of actual entries. The overall structure is captured in
|
||||
Fig 2 and the details of the header and entries are covered later in this
|
||||
section. The memory format of all shared structures is little-endian.
|
||||
|
||||
-----------------------------------------------
|
||||
| SMEM item A->B |
|
||||
| |
|
||||
| ----------------------------------------- |
|
||||
| |31 24| 16| 8| 0| |
|
||||
| |----------|---------|----------|---------| |
|
||||
| | Identifier Constant(Magic Number) | |
|
||||
| |----------|---------|----------|---------| |
|
||||
| | Feature Flags |Version | |
|
||||
| | |Number | |
|
||||
| |----------|---------|----------|---------| |
|
||||
| | Remote Proc ID |Local Proc ID | |
|
||||
| |----------|---------|----------|---------| |
|
||||
| | Entries Valid | Entries Total | |
|
||||
| |-----------------------------------------| |
|
||||
| |
|
||||
| |
|
||||
| ----------------------------------------- |
|
||||
| | Entry 0 | |
|
||||
| | ---------------------------------- | |
|
||||
| | | Identifier String | | |
|
||||
| | |---------------------------------| | |
|
||||
| | | Data | | |
|
||||
| | |---------------------------------| | |
|
||||
| |---------------------------------------| |
|
||||
| ----------------------------------------- |
|
||||
| | Entry 1 | |
|
||||
| | ---------------------------------- | |
|
||||
| | | Identifier String | | |
|
||||
| | |---------------------------------| | |
|
||||
| | | Data | | |
|
||||
| | |---------------------------------| | |
|
||||
| |---------------------------------------| |
|
||||
| - |
|
||||
| - |
|
||||
| - |
|
||||
| ----------------------------------------- |
|
||||
| | Entry N | |
|
||||
| | ---------------------------------- | |
|
||||
| | | Identifier String | | |
|
||||
| | |---------------------------------| | |
|
||||
| | | Data | | |
|
||||
| | |---------------------------------| | |
|
||||
| |---------------------------------------| |
|
||||
-----------------------------------------------
|
||||
|
||||
Fig 2
|
||||
|
||||
|
||||
The header of each SMEM item contains metadata that describes the processors
|
||||
using the edge, the version information, and the entry count. The constant
|
||||
identifier is used as a magic number to enable extraction of the items from a
|
||||
memory dump. The size of each entry depends upon the version, but the number
|
||||
of total entries (and hence the size of each SMEM item) is configurable with a
|
||||
suggested value of 16.
|
||||
|
||||
The number of valid entries is used to indicate how many of the Entries Total
|
||||
are currently used and are current valid.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|Field Size Description Valid Values |
|
||||
---------------------------------------------------------------------------
|
||||
| Identifier 4 Bytes Value used to identify |
|
||||
| Constant structure in memory. Must be set to $SMP |
|
||||
| Useful for debugging. (0x504D5324) |
|
||||
---------------------------------------------------------------------------
|
||||
| Local 2 Bytes Writing processor ID. Refer Processor ID Table 3|
|
||||
| Processor |
|
||||
| ID |
|
||||
---------------------------------------------------------------------------
|
||||
| Remote 2 Bytes Reading processor ID. Refer Processor ID Table 3|
|
||||
| Processor |
|
||||
| ID |
|
||||
---------------------------------------------------------------------------
|
||||
| Version 1 Bytes Refer to Version |
|
||||
| Number Feature Negotiation Must be set to 1. |
|
||||
| section. |
|
||||
---------------------------------------------------------------------------
|
||||
| Feature 3 Bytes Refer to Version |
|
||||
| flags and Feature Negotiation Must be set to zero. |
|
||||
| section. |
|
||||
---------------------------------------------------------------------------
|
||||
| Entries 2 Bytes Total number of Must be 0 or greater. |
|
||||
| Total entries. |
|
||||
---------------------------------------------------------------------------
|
||||
| Entries 2 Bytes Number of valid Must be between 0 |
|
||||
| Valid entries. and Entries Total. |
|
||||
---------------------------------------------------------------------------
|
||||
| Reserved 4 Bytes Reserved Must be set to 0. |
|
||||
---------------------------------------------------------------------------
|
||||
Table 1 - SMEM Item Header
|
||||
|
||||
The content of each SMEM entries is described in Table 2 and consists of a
|
||||
string identifier and a 32-bit data value. The string identifier must be
|
||||
unique for each SMEM item. The data value is opaque to SMP2P giving the client
|
||||
complete flexibility as to its usage.
|
||||
|
||||
----------------------- --------------------- -----------------------------
|
||||
| Field | Size | Description | Valid Values |
|
||||
------------|----------|---------------------|-----------------------------
|
||||
| | | | |
|
||||
| Identifier | 16 Bytes | Null Terminated | NON-NULL for |
|
||||
| String | | ASCII string. | valid entries. |
|
||||
| | | | |
|
||||
------------|----------|---------------------|-----------------------------
|
||||
| Data | 4 Bytes | Data | Any (client defined) |
|
||||
------------ ---------- --------------------- -----------------------------
|
||||
Table 2 - Entry Format
|
||||
|
||||
|
||||
The processor IDs in the system are fixed and new processors IDs will be
|
||||
added to the end of the list (Table 3).
|
||||
|
||||
-------------------------------------------------
|
||||
| Processor Name | ID value |
|
||||
-------------------------------------------------
|
||||
| Application processor | 0 |
|
||||
-------------------------------------------------
|
||||
| Modem processor | 1 |
|
||||
-------------------------------------------------
|
||||
| Audio processor | 2 |
|
||||
-------------------------------------------------
|
||||
| Sensor processor | 3 |
|
||||
-------------------------------------------------
|
||||
| Wireless processor | 4 |
|
||||
-------------------------------------------------
|
||||
| Modem Fw | 5 |
|
||||
-------------------------------------------------
|
||||
| Power processor | 6 |
|
||||
-------------------------------------------------
|
||||
| NUM PROCESSORS | 7 |
|
||||
-------------------------------------------------
|
||||
Table 3 - Processor IDs
|
||||
|
||||
SMEM Item
|
||||
---------
|
||||
The responsibility of creating an SMEM item is with the local processor that is
|
||||
initiating outbound traffic. After creating the item, the local and remote
|
||||
processors negotiate the version and feature flags for the item to ensure
|
||||
compatibility.
|
||||
|
||||
Table 4 lists the SMEM item base identifiers. To get the SMEM item ID for a
|
||||
particular edge, the remote processor ID (Table 3) is added to the base item ID
|
||||
for the local processor (Table 4). For example, the Apps ==> Modem (id 1) SMEM
|
||||
Item ID will be 427 + 1 = 428.
|
||||
|
||||
--------------------------------------------------
|
||||
| Description | SMEM ID value |
|
||||
--------------------------------------------------
|
||||
| Apps SMP2P SMEM Item base | 427 |
|
||||
--------------------------------------------------
|
||||
| Modem SMP2P SMEM Item base | 435 |
|
||||
--------------------------------------------------
|
||||
| Audio SMP2P SMEM Item base | 443 |
|
||||
--------------------------------------------------
|
||||
| Wireless SMP2P SMEM Item base | 451 |
|
||||
--------------------------------------------------
|
||||
| Power SMP2P SMEM Item base | 459 |
|
||||
--------------------------------------------------
|
||||
Table 4 - SMEM Items Base IDs
|
||||
|
||||
|
||||
Version and Feature Negotiation
|
||||
-------------------------------
|
||||
To enable upgrading without breaking the system and to enable graceful feature
|
||||
fall-back support, SMP2P supports a version number and feature flags. The
|
||||
combination of the version number and feature flags enable:
|
||||
1) SMP2P software updates to be rolled out to each processor separately.
|
||||
2) Individual features to be enabled or disabled per connection or edge.
|
||||
|
||||
The version number represents any change in SMP2P that breaks compatibility
|
||||
between processors. Examples would be a change in the shared data structures
|
||||
or changes to fundamental behavior. Each implementation of SMP2P must be able
|
||||
to support a minimum of the current version and the previous version.
|
||||
|
||||
The feature flags represent any changes in SMP2P that are optional and
|
||||
backwards compatible. Endpoints will negotiate the supported flag when the
|
||||
SMEM items are created and they cannot be changed after negotiation has been
|
||||
completed.
|
||||
|
||||
|
||||
Negotiation Algorithm
|
||||
----------------------
|
||||
While creating the SMEM item the following algorithm shall be used.
|
||||
|
||||
if remote endpoint's SMEM Item exists
|
||||
Read remote version number and flags
|
||||
Local version number must be lower of
|
||||
- remote version number
|
||||
- highest supported local version number
|
||||
Flags value is bitwise AND of
|
||||
- remote feature flags
|
||||
- locally supported flags
|
||||
Create SMEM item and populate negotiated number and flags
|
||||
Interrupt remote processor
|
||||
if version and flags match, negotiation is complete, else wait
|
||||
for remote interrupt below.
|
||||
Else
|
||||
Create SMEM item and populate it with highest supported version and any
|
||||
requested feature flag.
|
||||
Interrupt remote processor.
|
||||
Wait for Interrupt below.
|
||||
|
||||
Upon receiving the interrupt from remote processor and negotiation is not
|
||||
complete, check the version number and feature flags:
|
||||
if equal, negotiation is complete.
|
||||
if remote number is less than local number, and remote number is
|
||||
supported:
|
||||
Set local version number to remote version number
|
||||
Bitwise AND local flags with remote flags
|
||||
Interrupt remote processor
|
||||
Negotiation is complete
|
||||
if remote number is not supported, then negotiation has failed
|
||||
Set version number to 0xFF and report failure in kernel log.
|
||||
if remote number is more than local number:
|
||||
Wait for remote endpoint to process our interrupt and negotiate down.
|
||||
|
||||
|
||||
Creating an SMEM Entry
|
||||
----------------------
|
||||
Each new SMEM entry used in data transfer must be created at the end of the
|
||||
entry array in the SMEM item and cannot be deleted until the system is
|
||||
rebooted. The following sequence is be followed:
|
||||
1) Compare Entries Valid and Entries Total to verify if there is room in the
|
||||
entry array for this request (if not, return error code to client).
|
||||
2) Populate the Identifier of new entry and do a write memory barrier.
|
||||
3) Update Entries Valid and Entries Total and do a write memory barrier.
|
||||
4) Interrupt remote endpoint.
|
||||
|
||||
|
||||
Entry Write
|
||||
-----------
|
||||
An entry write is achieved by the following sequence of operations:
|
||||
1) Update data field in the entry and do a write memory barrier.
|
||||
2) Interrupt remote endpoint.
|
||||
|
||||
|
||||
Entry Read / Receiving Interrupts
|
||||
---------------------------------
|
||||
An interrupt will be received from the remote system for one or more of the following events:
|
||||
1) Initialization
|
||||
2) Entry change
|
||||
3) New Entry
|
||||
|
||||
As long as the SMEM item initialization is complete, then each interrupt should
|
||||
trigger SMP2P to:
|
||||
1) Compare valid entry data value to cached value and notify client if it
|
||||
has changed.
|
||||
2) Compare Entries Valid to cached value. If changed, initialize new entries.
|
||||
|
||||
Performance
|
||||
===========
|
||||
No performance issues are anticipated as the signaling rate is expected to be
|
||||
low and is performed in interrupt context which minimizes latency.
|
||||
|
||||
Interfaces
|
||||
================
|
||||
SMP2P is only supported in the kernel and interfaces with clients through the
|
||||
GPIO and interrupt subsystems.
|
||||
|
||||
To map an entry to the client, the client must add two nodes to the Device
|
||||
Tree:
|
||||
1) A node that matches "qcom,smp2pgpio" to create the entry
|
||||
2) A node that matches the client driver to provide the GPIO pin mapping
|
||||
|
||||
The details of the device tree entries are contained in the file
|
||||
Documentionat/devicetree/bindings/arm/msm/smp2p.txt.
|
||||
|
||||
|
||||
/* SMP2P Test Driver for inbound entry. */
|
||||
smp2pgpio_smp2p_7_in: qcom,smp2pgpio-smp2p-7-in {
|
||||
compatible = "qcom,smp2pgpio";
|
||||
qcom,entry-name = "smp2p";
|
||||
qcom,remote-pid = <7>;
|
||||
qcom,is_inbound;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
|
||||
/* SMP2P Test Client for inbound entry. */
|
||||
qcom,smp2pgpio_test_smp2p_7_in {
|
||||
compatible = "qcom,smp2pgpio_test_smp2p_7_in";
|
||||
gpios = <&smp2pgpio_smp2p_7_in 0 0>,
|
||||
<&smp2pgpio_smp2p_7_in 1 0>,
|
||||
. . .
|
||||
<&smp2pgpio_smp2p_7_in 31 0>;
|
||||
};
|
||||
|
||||
/* SMP2P Test Driver for outbound entries */
|
||||
smp2pgpio_smp2p_345_out: qcom,smp2pgpio-smp2p-7-out {
|
||||
compatible = "qcom,smp2pgpio";
|
||||
qcom,entry-name = "smp2p";
|
||||
qcom,remote-pid = <7>;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
|
||||
/* SMP2P Test Client for outbound entry. */
|
||||
qcom,smp2pgpio_test_smp2p_7_out {
|
||||
compatible = "qcom,smp2pgpio_test_smp2p_7_out";
|
||||
gpios = <&smp2pgpio_smp2p_7_out 0 0>,
|
||||
<&smp2pgpio_smp2p_7_out 1 0>,
|
||||
. . .
|
||||
<&smp2pgpio_smp2p_7_out 31 0>;
|
||||
|
||||
The client can use a match entry for "qcom,smp2pgpio_test_smp2p_7_in" to
|
||||
retrieve the Device Tree configuration node. Once that node has been
|
||||
retrieved, the client can call of_get_gpio() to get the virtual GPIO pin and
|
||||
also use gpio_to_irq() to map the GPIO pin to a virtual interrupt. After that
|
||||
point, the standard GPIO and Interrupt APIs can be used to manipulate the SMP2P
|
||||
entries and receive notifications of changes. Examples of typical function
|
||||
calls are shown below:
|
||||
of_get_gpio()
|
||||
gpio_get_value()
|
||||
gpio_set_value()
|
||||
gpio_to_irq()
|
||||
request_irq()
|
||||
free_irq()
|
||||
|
||||
Please reference the unit test code for example usage.
|
||||
|
||||
Debug
|
||||
=====
|
||||
The state values and names for all entries accessible by the Apps are
|
||||
accessible through debugfs nodes for general debug purposes.
|
||||
|
||||
Debugfs entries for triggering unit-tests are also exported.
|
||||
|
||||
Internal logging will be performed using the IPC Logging module to enable
|
||||
post-mortem analysis.
|
||||
|
||||
Testing
|
||||
=======
|
||||
On-target unit testing will be done to verify internal functionality and the
|
||||
GPIO/IRQ API's.
|
||||
|
||||
Driver parameters
|
||||
=================
|
||||
One module parameter will be provided to change the verbosity of internal logging.
|
||||
|
||||
Config options
|
||||
==============
|
||||
Configuration of interrupts will be done using Device Tree. By default, the
|
||||
testing components will be enabled since it does not affect performance and has
|
||||
a minimal impact on kernel size. However, customers can disable the testing
|
||||
components for size optimization.
|
||||
|
||||
CONFIG_MSM_SMP2P - enables SMP2P core functionality
|
||||
CONFIG_MSM_SMP2P_TEST - enables unit test support
|
||||
|
||||
Dependencies
|
||||
===========
|
||||
Requires SMEM for creating the SMEM items.
|
||||
|
||||
User Space utilities
|
||||
====================
|
||||
No userspace utilities are planned.
|
||||
|
||||
Known issues
|
||||
============
|
||||
None.
|
|
@ -0,0 +1,21 @@
|
|||
Qualcomm SMSM Point-to-Point (SMP2P)
|
||||
|
||||
Required properties:
|
||||
-compatible : should be "qcom,smp2p"
|
||||
-reg : the location and offset of the irq register base memory
|
||||
-reg-names : "irq-reg-base", "irq-reg-offset" - string to identify the irq
|
||||
register region and offset values
|
||||
-qcom,remote-pid : the SMP2P remote processor ID (see smp2p_private_api.h)
|
||||
-qcom,irq-bitmask : the sending irq bitmask
|
||||
-interrupts : the receiving interrupt line
|
||||
|
||||
Example:
|
||||
|
||||
qcom,smp2p-modem {
|
||||
compatible = "qcom,smp2p";
|
||||
reg = <0xfa006000 0x1000>, <0x8 0x0>;
|
||||
reg-names = "irq-reg-base", "irq-reg-offset";
|
||||
qcom,remote-pid = <1>;
|
||||
qcom,irq-bitmask = <0x4000>;
|
||||
interrupts = <0 27 1>;
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
Qualcomm SMSM Point-to-Point (SMP2P) GPIO Driver
|
||||
|
||||
Used to map an SMP2P entry and remote processor ID to a virtual GPIO controller
|
||||
and virtual interrupt controller.
|
||||
|
||||
Required properties:
|
||||
-compatible : should be "qcom,smp2pgpio";
|
||||
-qcom,entry-name : name of the SMP2P entry
|
||||
-qcom,remote-pid : the SMP2P remote processor ID (see smp2p_private_api.h)
|
||||
-gpio-controller : specifies that this is a GPIO controller
|
||||
-#gpio-cells : number of GPIO cells (should always be <2>)
|
||||
-interrupt-controller : specifies that this is an interrupt controller
|
||||
-#interrupt-cells : number of interrupt cells (should always be <2>)
|
||||
|
||||
Optional properties:
|
||||
-qcom,is-inbound : specifies that this is an inbound entry (default is outbound)
|
||||
|
||||
Comments:
|
||||
All device tree entries must be unique. Therefore to prevent naming collisions
|
||||
between clients, it is recommended that the DT nodes should be named using the
|
||||
format:
|
||||
smp2pgpio_<ENTRY_NAME>_<REMOTE PID>_<in|out>
|
||||
|
||||
Example:
|
||||
/* Maps inbound "smp2p" entry on remote PID 7 to GPIO controller. */
|
||||
smp2pgpio_smp2p_7_in: qcom,smp2pgpio-smp2p-7-in {
|
||||
compatible = "qcom,smp2pgpio";
|
||||
qcom,entry-name = "smp2p";
|
||||
qcom,remote-pid = <7>;
|
||||
qcom,is-inbound;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
|
||||
/*
|
||||
* Maps inbound "smp2p" entry on remote PID 7 to client driver
|
||||
* "qcom,smp2pgpio_test_smp2p_7_in".
|
||||
*
|
||||
* Note: If all 32-pins are used by this client, then you
|
||||
* can just list pin 0 here as a shortcut.
|
||||
*/
|
||||
qcom,smp2pgpio_test_smp2p_7_in {
|
||||
compatible = "qcom,smp2pgpio_test_smp2p_7_in";
|
||||
gpios = <&smp2pgpio_smp2p_7_in 0 0>, /* pin 0 */
|
||||
<&smp2pgpio_smp2p_7_in 1 0>,
|
||||
. . .
|
||||
<&smp2pgpio_smp2p_7_in 31 0>; /* pin 31 */
|
||||
};
|
||||
|
||||
|
||||
/* Maps outbound "smp2p" entry on remote PID 7 to GPIO controller. */
|
||||
smp2pgpio_smp2p_7_out: qcom,smp2pgpio-smp2p-7-out {
|
||||
compatible = "qcom,smp2pgpio";
|
||||
qcom,entry-name = "smp2p";
|
||||
qcom,remote-pid = <7>;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
|
||||
/*
|
||||
* Maps outbound "smp2p" entry on remote PID 7 to client driver
|
||||
* "qcom,smp2pgpio_test_smp2p_7_out".
|
||||
*
|
||||
* Note: If all 32-pins are used by this client, then you
|
||||
* can just list pin 0 here as a shortcut.
|
||||
*/
|
||||
qcom,smp2pgpio_test_smp2p_7_out {
|
||||
compatible = "qcom,smp2pgpio_test_smp2p_7_out";
|
||||
gpios = <&smp2pgpio_smp2p_7_out 0 0>, /* pin 0 */
|
||||
<&smp2pgpio_smp2p_7_out 1 0>,
|
||||
. . .
|
||||
<&smp2pgpio_smp2p_7_out 31 0>; /* pin 31 */
|
||||
};
|
|
@ -1447,6 +1447,27 @@ config MSM_N_WAY_SMSM
|
|||
Supports APPS-QDSP SMSM communication along with
|
||||
normal APPS-MODEM SMSM communication.
|
||||
|
||||
config MSM_SMP2P
|
||||
bool "SMSM Point-to-Point (SMP2P)"
|
||||
depends on MSM_SMD
|
||||
help
|
||||
Provide point-to-point remote signaling support.
|
||||
SMP2P enables transferring 32-bit values between
|
||||
the local and a remote system using shared
|
||||
memory and interrupts. A client can open multiple
|
||||
32-bit values by specifying a unique string and
|
||||
remote processor ID.
|
||||
|
||||
config MSM_SMP2P_TEST
|
||||
bool "SMSM Point-to-Point Test"
|
||||
depends on MSM_SMP2P
|
||||
help
|
||||
Enables loopback and unit testing support for
|
||||
SMP2P. Loopback support is used by other
|
||||
processors to do unit testing. Unit tests
|
||||
are used to verify the local and remote
|
||||
implementations.
|
||||
|
||||
config MSM_RESET_MODEM
|
||||
tristate "Reset Modem Driver"
|
||||
depends on MSM_SMD
|
||||
|
|
|
@ -69,7 +69,8 @@ $(obj)/smd_rpc_sym.c: $(src)/smd_rpc_sym $(src)/mkrpcsym.pl
|
|||
$(call if_changed,mkrpcsym)
|
||||
|
||||
obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o remote_spinlock.o smd_private.o
|
||||
|
||||
obj-$(CONFIG_MSM_SMP2P) += smp2p.o smp2p_debug.o smp2p_gpio.o
|
||||
obj-$(CONFIG_MSM_SMP2P_TEST) += smp2p_loopback.o smp2p_test.o smp2p_gpio_test.o smp2p_spinlock_test.o
|
||||
obj-$(CONFIG_MSM_SCM) += scm.o scm-boot.o
|
||||
obj-$(CONFIG_MSM_SECURE_IO) += scm-io.o
|
||||
obj-$(CONFIG_MSM_PIL) += peripheral-loader.o
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
@ -198,7 +198,11 @@ enum {
|
|||
SMEM_SSR_REASON_LPASS0,
|
||||
SMEM_SSR_REASON_DSPS0,
|
||||
SMEM_SSR_REASON_VCODEC0,
|
||||
SMEM_MEM_LAST = SMEM_SSR_REASON_VCODEC0,
|
||||
SMEM_SMP2P_APPS_BASE = 427,
|
||||
SMEM_SMP2P_MODEM_BASE = 435,
|
||||
SMEM_SMP2P_AUDIO_BASE = 443,
|
||||
SMEM_SMP2P_WIRLESS_BASE = 451,
|
||||
SMEM_SMP2P_POWER_BASE = 459,
|
||||
SMEM_NUM_ITEMS,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* arch/arm/mach-msm/smd.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
* Copyright (c) 2008-2012, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2008-2013, The Linux Foundation. All rights reserved.
|
||||
* Author: Brian Swetland <swetland@google.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
|
@ -3152,6 +3152,17 @@ int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask,
|
|||
}
|
||||
EXPORT_SYMBOL(smsm_state_cb_deregister);
|
||||
|
||||
/**
|
||||
* smem_get_remote_spinlock - Remote spinlock pointer for unit testing.
|
||||
*
|
||||
* @returns: pointer to SMEM remote spinlock
|
||||
*/
|
||||
remote_spinlock_t *smem_get_remote_spinlock(void)
|
||||
{
|
||||
return &remote_spinlock;
|
||||
}
|
||||
EXPORT_SYMBOL(smem_get_remote_spinlock);
|
||||
|
||||
int smd_module_init_notifier_register(struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* arch/arm/mach-msm/smd_private.h
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
* Copyright (c) 2007-2012, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2007-2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include <linux/types.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/remote_spinlock.h>
|
||||
#include <mach/msm_smsm.h>
|
||||
#include <mach/msm_smd.h>
|
||||
|
||||
|
@ -271,4 +273,6 @@ struct interrupt_stat {
|
|||
};
|
||||
extern struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS];
|
||||
|
||||
/* used for unit testing spinlocks */
|
||||
remote_spinlock_t *smem_get_remote_spinlock(void);
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,328 @@
|
|||
/* arch/arm/mach-msm/smp2p_debug.c
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/ctype.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include "smp2p_private.h"
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS)
|
||||
|
||||
/**
|
||||
* Dump interrupt statistics.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*/
|
||||
static void smp2p_int_stats(struct seq_file *s)
|
||||
{
|
||||
struct smp2p_interrupt_config *int_cfg;
|
||||
int pid;
|
||||
|
||||
int_cfg = smp2p_get_interrupt_config();
|
||||
if (!int_cfg)
|
||||
return;
|
||||
|
||||
seq_printf(s, "| Processor | Incoming Id | Incoming # |");
|
||||
seq_printf(s, " Outgoing # | Base Ptr | Mask |\n");
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
|
||||
if (!int_cfg[pid].is_configured &&
|
||||
pid != SMP2P_REMOTE_MOCK_PROC)
|
||||
continue;
|
||||
|
||||
seq_printf(s,
|
||||
"| %5s (%d) | %11u | %10u | %10u | %p | %08x |\n",
|
||||
int_cfg[pid].name,
|
||||
pid, int_cfg[pid].in_int_id,
|
||||
int_cfg[pid].in_interrupt_count,
|
||||
int_cfg[pid].out_interrupt_count,
|
||||
int_cfg[pid].out_int_ptr,
|
||||
int_cfg[pid].out_int_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump item header line 1.
|
||||
*
|
||||
* @buf: output buffer
|
||||
* @max: length of output buffer
|
||||
* @item_ptr: SMEM item pointer
|
||||
* @state: item state
|
||||
* @returns: Number of bytes written to output buffer
|
||||
*/
|
||||
static int smp2p_item_header1(char *buf, int max, struct smp2p_smem *item_ptr,
|
||||
enum msm_smp2p_edge_state state)
|
||||
{
|
||||
int i = 0;
|
||||
const char *state_text;
|
||||
|
||||
if (!item_ptr) {
|
||||
i += scnprintf(buf + i, max - i, "None");
|
||||
return i;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case SMP2P_EDGE_STATE_CLOSED:
|
||||
state_text = "State: Closed";
|
||||
break;
|
||||
case SMP2P_EDGE_STATE_OPENING:
|
||||
state_text = "State: Opening";
|
||||
break;
|
||||
case SMP2P_EDGE_STATE_OPENED:
|
||||
state_text = "State: Opened";
|
||||
break;
|
||||
default:
|
||||
state_text = "";
|
||||
break;
|
||||
}
|
||||
|
||||
i += scnprintf(buf + i, max - i,
|
||||
"%-14s LPID %d RPID %d",
|
||||
state_text,
|
||||
SMP2P_GET_LOCAL_PID(item_ptr->rem_loc_proc_id),
|
||||
SMP2P_GET_REMOTE_PID(item_ptr->rem_loc_proc_id)
|
||||
);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump item header line 2.
|
||||
*
|
||||
* @buf: output buffer
|
||||
* @max: length of output buffer
|
||||
* @item_ptr: SMEM item pointer
|
||||
* @returns: Number of bytes written to output buffer
|
||||
*/
|
||||
static int smp2p_item_header2(char *buf, int max, struct smp2p_smem *item_ptr)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!item_ptr) {
|
||||
i += scnprintf(buf + i, max - i, "None");
|
||||
return i;
|
||||
}
|
||||
|
||||
i += scnprintf(buf + i, max - i,
|
||||
"Version: %08x Features: %08x",
|
||||
SMP2P_GET_VERSION(item_ptr->feature_version),
|
||||
SMP2P_GET_FEATURES(item_ptr->feature_version)
|
||||
);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump item header line 3.
|
||||
*
|
||||
* @buf: output buffer
|
||||
* @max: length of output buffer
|
||||
* @item_ptr: SMEM item pointer
|
||||
* @state: item state
|
||||
* @returns: Number of bytes written to output buffer
|
||||
*/
|
||||
static int smp2p_item_header3(char *buf, int max, struct smp2p_smem *item_ptr)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!item_ptr) {
|
||||
i += scnprintf(buf + i, max - i, "None");
|
||||
return i;
|
||||
}
|
||||
|
||||
i += scnprintf(buf + i, max - i,
|
||||
"Entries Valid/Max: %d/%d",
|
||||
SMP2P_GET_ENT_VALID(item_ptr->valid_total_ent),
|
||||
SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent)
|
||||
);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump individual input/output item pair.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*/
|
||||
static void smp2p_item(struct seq_file *s, int remote_pid)
|
||||
{
|
||||
struct smp2p_smem *out_ptr;
|
||||
struct smp2p_smem *in_ptr;
|
||||
struct smp2p_interrupt_config *int_cfg;
|
||||
char tmp_buff[64];
|
||||
int state;
|
||||
int entry;
|
||||
struct smp2p_entry_v1 *out_entries = NULL;
|
||||
struct smp2p_entry_v1 *in_entries = NULL;
|
||||
int out_valid = 0;
|
||||
int in_valid = 0;
|
||||
|
||||
int_cfg = smp2p_get_interrupt_config();
|
||||
if (!int_cfg)
|
||||
return;
|
||||
if (!int_cfg[remote_pid].is_configured &&
|
||||
remote_pid != SMP2P_REMOTE_MOCK_PROC)
|
||||
return;
|
||||
|
||||
out_ptr = smp2p_get_out_item(remote_pid, &state);
|
||||
in_ptr = smp2p_get_in_item(remote_pid);
|
||||
|
||||
if (!out_ptr && !in_ptr)
|
||||
return;
|
||||
|
||||
/* print item headers */
|
||||
seq_printf(s, "%s%s\n",
|
||||
" ====================================== ",
|
||||
"======================================");
|
||||
scnprintf(tmp_buff, sizeof(tmp_buff),
|
||||
"Apps(%d)->%s(%d)",
|
||||
SMP2P_APPS_PROC, int_cfg[remote_pid].name, remote_pid);
|
||||
seq_printf(s, "| %-37s", tmp_buff);
|
||||
|
||||
scnprintf(tmp_buff, sizeof(tmp_buff),
|
||||
"%s(%d)->Apps(%d)",
|
||||
int_cfg[remote_pid].name, remote_pid, SMP2P_APPS_PROC);
|
||||
seq_printf(s, "| %-37s|\n", tmp_buff);
|
||||
seq_printf(s, "%s%s\n",
|
||||
" ====================================== ",
|
||||
"======================================");
|
||||
|
||||
smp2p_item_header1(tmp_buff, sizeof(tmp_buff), out_ptr, state);
|
||||
seq_printf(s, "| %-37s", tmp_buff);
|
||||
smp2p_item_header1(tmp_buff, sizeof(tmp_buff), in_ptr, -1);
|
||||
seq_printf(s, "| %-37s|\n", tmp_buff);
|
||||
|
||||
smp2p_item_header2(tmp_buff, sizeof(tmp_buff), out_ptr);
|
||||
seq_printf(s, "| %-37s", tmp_buff);
|
||||
smp2p_item_header2(tmp_buff, sizeof(tmp_buff), in_ptr);
|
||||
seq_printf(s, "| %-37s|\n", tmp_buff);
|
||||
|
||||
smp2p_item_header3(tmp_buff, sizeof(tmp_buff), out_ptr);
|
||||
seq_printf(s, "| %-37s", tmp_buff);
|
||||
smp2p_item_header3(tmp_buff, sizeof(tmp_buff), in_ptr);
|
||||
seq_printf(s, "| %-37s|\n", tmp_buff);
|
||||
|
||||
seq_printf(s, " %s%s\n",
|
||||
"-------------------------------------- ",
|
||||
"--------------------------------------");
|
||||
seq_printf(s, "| %-37s",
|
||||
"Entry Name Value");
|
||||
seq_printf(s, "| %-37s|\n",
|
||||
"Entry Name Value");
|
||||
seq_printf(s, " %s%s\n",
|
||||
"-------------------------------------- ",
|
||||
"--------------------------------------");
|
||||
|
||||
/* print entries */
|
||||
if (out_ptr) {
|
||||
out_entries = (struct smp2p_entry_v1 *)((void *)out_ptr +
|
||||
sizeof(struct smp2p_smem));
|
||||
out_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent);
|
||||
}
|
||||
|
||||
if (in_ptr) {
|
||||
in_entries = (struct smp2p_entry_v1 *)((void *)in_ptr +
|
||||
sizeof(struct smp2p_smem));
|
||||
in_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent);
|
||||
}
|
||||
|
||||
for (entry = 0; out_entries || in_entries; ++entry) {
|
||||
if (out_entries && entry < out_valid) {
|
||||
scnprintf(tmp_buff, sizeof(tmp_buff),
|
||||
"%-16s 0x%08x",
|
||||
out_entries->name,
|
||||
out_entries->entry);
|
||||
++out_entries;
|
||||
} else {
|
||||
out_entries = NULL;
|
||||
scnprintf(tmp_buff, sizeof(tmp_buff), "None");
|
||||
}
|
||||
seq_printf(s, "| %-37s", tmp_buff);
|
||||
|
||||
if (in_entries && entry < in_valid) {
|
||||
scnprintf(tmp_buff, sizeof(tmp_buff),
|
||||
"%-16s 0x%08x",
|
||||
in_entries->name,
|
||||
in_entries->entry);
|
||||
++in_entries;
|
||||
} else {
|
||||
in_entries = NULL;
|
||||
scnprintf(tmp_buff, sizeof(tmp_buff), "None");
|
||||
}
|
||||
seq_printf(s, "| %-37s|\n", tmp_buff);
|
||||
}
|
||||
seq_printf(s, " %s%s\n\n",
|
||||
"-------------------------------------- ",
|
||||
"--------------------------------------");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump item state.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*/
|
||||
static void smp2p_items(struct seq_file *s)
|
||||
{
|
||||
int pid;
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
|
||||
smp2p_item(s, pid);
|
||||
}
|
||||
|
||||
static struct dentry *dent;
|
||||
|
||||
static int debugfs_show(struct seq_file *s, void *data)
|
||||
{
|
||||
void (*show)(struct seq_file *) = s->private;
|
||||
|
||||
show(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, debugfs_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations debug_ops = {
|
||||
.open = debug_open,
|
||||
.release = single_release,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
};
|
||||
|
||||
void debug_create(const char *name,
|
||||
void (*show)(struct seq_file *))
|
||||
{
|
||||
struct dentry *file;
|
||||
|
||||
file = debugfs_create_file(name, 0444, dent, show, &debug_ops);
|
||||
if (!file)
|
||||
pr_err("%s: unable to create file '%s'\n", __func__, name);
|
||||
}
|
||||
|
||||
static int __init smp2p_debugfs_init(void)
|
||||
{
|
||||
dent = debugfs_create_dir("smp2p", 0);
|
||||
if (IS_ERR(dent))
|
||||
return PTR_ERR(dent);
|
||||
|
||||
debug_create("int_stats", smp2p_int_stats);
|
||||
debug_create("items", smp2p_items);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
late_initcall(smp2p_debugfs_init);
|
||||
#endif /* CONFIG_DEBUG_FS */
|
|
@ -0,0 +1,757 @@
|
|||
/* arch/arm/mach-msm/smp2p_gpio.c
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <mach/msm_ipc_logging.h>
|
||||
#include "smp2p_private_api.h"
|
||||
#include "smp2p_private.h"
|
||||
|
||||
/* GPIO device - one per SMP2P entry. */
|
||||
struct smp2p_chip_dev {
|
||||
struct list_head entry_list;
|
||||
char name[SMP2P_MAX_ENTRY_NAME];
|
||||
int remote_pid;
|
||||
bool is_inbound;
|
||||
bool is_open;
|
||||
struct notifier_block out_notifier;
|
||||
struct notifier_block in_notifier;
|
||||
struct msm_smp2p_out *out_handle;
|
||||
|
||||
struct gpio_chip gpio;
|
||||
struct irq_domain *irq_domain;
|
||||
int irq_base;
|
||||
|
||||
spinlock_t irq_lock;
|
||||
DECLARE_BITMAP(irq_enabled, SMP2P_BITS_PER_ENTRY);
|
||||
DECLARE_BITMAP(irq_rising_edge, SMP2P_BITS_PER_ENTRY);
|
||||
DECLARE_BITMAP(irq_falling_edge, SMP2P_BITS_PER_ENTRY);
|
||||
};
|
||||
|
||||
static struct platform_driver smp2p_gpio_driver;
|
||||
static struct lock_class_key smp2p_gpio_lock_class;
|
||||
static struct irq_chip smp2p_gpio_irq_chip;
|
||||
static DEFINE_SPINLOCK(smp2p_entry_lock_lha1);
|
||||
static LIST_HEAD(smp2p_entry_list);
|
||||
|
||||
/* Used for mapping edge to name for logging. */
|
||||
static const char * const edge_names[] = {
|
||||
"-",
|
||||
"0->1",
|
||||
"1->0",
|
||||
"-",
|
||||
};
|
||||
|
||||
/* Used for mapping edge to value for logging. */
|
||||
static const char * const edge_name_rising[] = {
|
||||
"-",
|
||||
"0->1",
|
||||
};
|
||||
|
||||
/* Used for mapping edge to value for logging. */
|
||||
static const char * const edge_name_falling[] = {
|
||||
"-",
|
||||
"1->0",
|
||||
};
|
||||
|
||||
static int smp2p_gpio_to_irq(struct gpio_chip *cp,
|
||||
unsigned offset);
|
||||
|
||||
/**
|
||||
* smp2p_get_value - Retrieves GPIO value.
|
||||
*
|
||||
* @cp: GPIO chip pointer
|
||||
* @offset: Pin offset
|
||||
* @returns: >=0: value of GPIO Pin; < 0 for error
|
||||
*
|
||||
* Error codes:
|
||||
* -ENODEV - chip/entry invalid
|
||||
* -ENETDOWN - valid entry, but entry not yet created
|
||||
*/
|
||||
static int smp2p_get_value(struct gpio_chip *cp,
|
||||
unsigned offset)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
int ret = 0;
|
||||
uint32_t data;
|
||||
|
||||
if (!cp)
|
||||
return -ENODEV;
|
||||
|
||||
chip = container_of(cp, struct smp2p_chip_dev, gpio);
|
||||
if (!chip->is_open)
|
||||
return -ENETDOWN;
|
||||
|
||||
if (chip->is_inbound)
|
||||
ret = msm_smp2p_in_read(chip->remote_pid, chip->name, &data);
|
||||
else
|
||||
ret = msm_smp2p_out_read(chip->out_handle, &data);
|
||||
|
||||
if (!ret)
|
||||
ret = (data & (1 << offset)) ? 1 : 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_set_value - Sets GPIO value.
|
||||
*
|
||||
* @cp: GPIO chip pointer
|
||||
* @offset: Pin offset
|
||||
* @value: New value
|
||||
*/
|
||||
static void smp2p_set_value(struct gpio_chip *cp, unsigned offset, int value)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
uint32_t data_set;
|
||||
uint32_t data_clear;
|
||||
int ret;
|
||||
|
||||
if (!cp)
|
||||
return;
|
||||
|
||||
chip = container_of(cp, struct smp2p_chip_dev, gpio);
|
||||
if (!chip->is_open)
|
||||
return;
|
||||
|
||||
if (chip->is_inbound) {
|
||||
SMP2P_ERR("%s: '%s':%d virq %d invalid operation\n",
|
||||
__func__, chip->name, chip->remote_pid,
|
||||
chip->irq_base + offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
data_set = 1 << offset;
|
||||
data_clear = 0;
|
||||
} else {
|
||||
data_set = 0;
|
||||
data_clear = 1 << offset;
|
||||
}
|
||||
|
||||
ret = msm_smp2p_out_modify(chip->out_handle,
|
||||
data_set, data_clear);
|
||||
|
||||
if (ret)
|
||||
SMP2P_GPIO("'%s':%d gpio %d set to %d failed (%d)\n",
|
||||
chip->name, chip->remote_pid,
|
||||
chip->gpio.base + offset, value, ret);
|
||||
else
|
||||
SMP2P_GPIO("'%s':%d gpio %d set to %d\n",
|
||||
chip->name, chip->remote_pid,
|
||||
chip->gpio.base + offset, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_direction_input - Sets GPIO direction to input.
|
||||
*
|
||||
* @cp: GPIO chip pointer
|
||||
* @offset: Pin offset
|
||||
* @returns: 0 for success; < 0 for failure
|
||||
*/
|
||||
static int smp2p_direction_input(struct gpio_chip *cp, unsigned offset)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
|
||||
if (!cp)
|
||||
return -ENODEV;
|
||||
|
||||
chip = container_of(cp, struct smp2p_chip_dev, gpio);
|
||||
if (!chip->is_inbound)
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_direction_output - Sets GPIO direction to output.
|
||||
*
|
||||
* @cp: GPIO chip pointer
|
||||
* @offset: Pin offset
|
||||
* @value: Direction
|
||||
* @returns: 0 for success; < 0 for failure
|
||||
*/
|
||||
static int smp2p_direction_output(struct gpio_chip *cp,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
|
||||
if (!cp)
|
||||
return -ENODEV;
|
||||
|
||||
chip = container_of(cp, struct smp2p_chip_dev, gpio);
|
||||
if (chip->is_inbound)
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_to_irq - Convert GPIO pin to virtual IRQ pin.
|
||||
*
|
||||
* @cp: GPIO chip pointer
|
||||
* @offset: Pin offset
|
||||
* @returns: >0 for virtual irq value; < 0 for failure
|
||||
*/
|
||||
static int smp2p_gpio_to_irq(struct gpio_chip *cp, unsigned offset)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
|
||||
chip = container_of(cp, struct smp2p_chip_dev, gpio);
|
||||
if (!cp || chip->irq_base <= 0)
|
||||
return -ENODEV;
|
||||
|
||||
return chip->irq_base + offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_irq_mask_helper - Mask/Unmask interrupt.
|
||||
*
|
||||
* @d: IRQ data
|
||||
* @mask: true to mask (disable), false to unmask (enable)
|
||||
*/
|
||||
static void smp2p_gpio_irq_mask_helper(struct irq_data *d, bool mask)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
int offset;
|
||||
unsigned long flags;
|
||||
|
||||
chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
|
||||
if (!chip || chip->irq_base <= 0)
|
||||
return;
|
||||
|
||||
offset = d->irq - chip->irq_base;
|
||||
spin_lock_irqsave(&chip->irq_lock, flags);
|
||||
if (mask)
|
||||
clear_bit(offset, chip->irq_enabled);
|
||||
else
|
||||
set_bit(offset, chip->irq_enabled);
|
||||
spin_unlock_irqrestore(&chip->irq_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_irq_mask - Mask interrupt.
|
||||
*
|
||||
* @d: IRQ data
|
||||
*/
|
||||
static void smp2p_gpio_irq_mask(struct irq_data *d)
|
||||
{
|
||||
smp2p_gpio_irq_mask_helper(d, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_irq_unmask - Unmask interrupt.
|
||||
*
|
||||
* @d: IRQ data
|
||||
*/
|
||||
static void smp2p_gpio_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
smp2p_gpio_irq_mask_helper(d, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_irq_set_type - Set interrupt edge type.
|
||||
*
|
||||
* @d: IRQ data
|
||||
* @type: Edge type for interrupt
|
||||
* @returns 0 for success; < 0 for failure
|
||||
*/
|
||||
static int smp2p_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
int offset;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
|
||||
if (!chip)
|
||||
return -ENODEV;
|
||||
|
||||
if (chip->irq_base <= 0) {
|
||||
SMP2P_ERR("%s: '%s':%d virqbase %d invalid\n",
|
||||
__func__, chip->name, chip->remote_pid,
|
||||
chip->irq_base);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
offset = d->irq - chip->irq_base;
|
||||
|
||||
spin_lock_irqsave(&chip->irq_lock, flags);
|
||||
clear_bit(offset, chip->irq_rising_edge);
|
||||
clear_bit(offset, chip->irq_falling_edge);
|
||||
switch (type) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
set_bit(offset, chip->irq_rising_edge);
|
||||
break;
|
||||
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
set_bit(offset, chip->irq_falling_edge);
|
||||
break;
|
||||
|
||||
case IRQ_TYPE_NONE:
|
||||
case IRQ_TYPE_DEFAULT:
|
||||
case IRQ_TYPE_EDGE_BOTH:
|
||||
set_bit(offset, chip->irq_rising_edge);
|
||||
set_bit(offset, chip->irq_falling_edge);
|
||||
break;
|
||||
|
||||
default:
|
||||
SMP2P_ERR("%s: unsupported interrupt type 0x%x\n",
|
||||
__func__, type);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->irq_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_irq_map - Creates or updates binding of virtual IRQ
|
||||
*
|
||||
* @domain_ptr: Interrupt domain pointer
|
||||
* @virq: Virtual IRQ
|
||||
* @hw: Hardware IRQ (same as virq for nomap)
|
||||
* @returns: 0 for success
|
||||
*/
|
||||
static int smp2p_irq_map(struct irq_domain *domain_ptr, unsigned int virq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
|
||||
chip = domain_ptr->host_data;
|
||||
if (!chip) {
|
||||
SMP2P_ERR("%s: invalid domain ptr %p\n", __func__, domain_ptr);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* map chip structures to device */
|
||||
irq_set_lockdep_class(virq, &smp2p_gpio_lock_class);
|
||||
irq_set_chip_and_handler(virq, &smp2p_gpio_irq_chip,
|
||||
handle_level_irq);
|
||||
irq_set_chip_data(virq, chip);
|
||||
set_irq_flags(virq, IRQF_VALID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip smp2p_gpio_irq_chip = {
|
||||
.name = "smp2p_gpio",
|
||||
.irq_mask = smp2p_gpio_irq_mask,
|
||||
.irq_unmask = smp2p_gpio_irq_unmask,
|
||||
.irq_set_type = smp2p_gpio_irq_set_type,
|
||||
};
|
||||
|
||||
/* No-map interrupt Domain */
|
||||
static const struct irq_domain_ops smp2p_irq_domain_ops = {
|
||||
.map = smp2p_irq_map,
|
||||
};
|
||||
|
||||
/**
|
||||
* msm_summary_irq_handler - Handles inbound entry change notification.
|
||||
*
|
||||
* @chip: GPIO chip pointer
|
||||
* @entry: Change notification data
|
||||
*
|
||||
* Whenever an entry changes, this callback is triggered to determine
|
||||
* which bits changed and if the corresponding interrupts need to be
|
||||
* triggered.
|
||||
*/
|
||||
static void msm_summary_irq_handler(struct smp2p_chip_dev *chip,
|
||||
struct msm_smp2p_update_notif *entry)
|
||||
{
|
||||
int i;
|
||||
uint32_t cur_val;
|
||||
uint32_t prev_val;
|
||||
uint32_t edge;
|
||||
unsigned long flags;
|
||||
bool trigger_interrrupt;
|
||||
bool irq_rising;
|
||||
bool irq_falling;
|
||||
|
||||
cur_val = entry->current_value;
|
||||
prev_val = entry->previous_value;
|
||||
|
||||
if (chip->irq_base <= 0)
|
||||
return;
|
||||
|
||||
SMP2P_GPIO("'%s':%d GPIO Summary IRQ Change %08x->%08x\n",
|
||||
chip->name, chip->remote_pid, prev_val, cur_val);
|
||||
|
||||
for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
|
||||
spin_lock_irqsave(&chip->irq_lock, flags);
|
||||
trigger_interrrupt = false;
|
||||
edge = (prev_val & 0x1) << 1 | (cur_val & 0x1);
|
||||
irq_rising = test_bit(i, chip->irq_rising_edge);
|
||||
irq_falling = test_bit(i, chip->irq_falling_edge);
|
||||
|
||||
if (test_bit(i, chip->irq_enabled)) {
|
||||
if (edge == 0x1 && irq_rising)
|
||||
/* 0->1 transition */
|
||||
trigger_interrrupt = true;
|
||||
else if (edge == 0x2 && irq_falling)
|
||||
/* 1->0 transition */
|
||||
trigger_interrrupt = true;
|
||||
} else {
|
||||
SMP2P_GPIO(
|
||||
"'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s disabled\n",
|
||||
chip->name, chip->remote_pid, i,
|
||||
chip->irq_base + i,
|
||||
edge_name_rising[irq_rising],
|
||||
edge_name_falling[irq_falling],
|
||||
edge_names[edge]);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->irq_lock, flags);
|
||||
|
||||
if (trigger_interrrupt) {
|
||||
SMP2P_GPIO(
|
||||
"'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s triggering\n",
|
||||
chip->name, chip->remote_pid, i,
|
||||
chip->irq_base + i,
|
||||
edge_name_rising[irq_rising],
|
||||
edge_name_falling[irq_falling],
|
||||
edge_names[edge]);
|
||||
(void)generic_handle_irq(chip->irq_base + i);
|
||||
}
|
||||
|
||||
cur_val >>= 1;
|
||||
prev_val >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an interrupt domain based upon the DT node.
|
||||
*
|
||||
* @chip: pointer to GPIO chip
|
||||
* @node: pointer to Device Tree node
|
||||
*/
|
||||
static void smp2p_add_irq_domain(struct smp2p_chip_dev *chip,
|
||||
struct device_node *node)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* map GPIO pins to interrupts */
|
||||
chip->irq_domain = irq_domain_add_nomap(node, 0,
|
||||
&smp2p_irq_domain_ops, chip);
|
||||
if (!chip->irq_domain) {
|
||||
SMP2P_ERR("%s: unable to create interrupt domain '%s':%d\n",
|
||||
__func__, chip->name, chip->remote_pid);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
|
||||
unsigned int virt_irq;
|
||||
|
||||
virt_irq = irq_create_direct_mapping(chip->irq_domain);
|
||||
if (virt_irq == NO_IRQ) {
|
||||
SMP2P_ERR("%s: gpio->virt IRQ mapping failed '%s':%d\n",
|
||||
__func__, chip->name, chip->remote_pid);
|
||||
} else if (!chip->irq_base) {
|
||||
chip->irq_base = virt_irq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifier function passed into smp2p API for out bound entries.
|
||||
*
|
||||
* @self: Pointer to calling notifier block
|
||||
* @event: Event
|
||||
* @data: Event-specific data
|
||||
* @returns: 0
|
||||
*/
|
||||
static int smp2p_gpio_out_notify(struct notifier_block *self,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
|
||||
chip = container_of(self, struct smp2p_chip_dev, out_notifier);
|
||||
|
||||
switch (event) {
|
||||
case SMP2P_OPEN:
|
||||
chip->is_open = 1;
|
||||
SMP2P_GPIO("%s: Opened out '%s':%d\n", __func__,
|
||||
chip->name, chip->remote_pid);
|
||||
break;
|
||||
case SMP2P_ENTRY_UPDATE:
|
||||
break;
|
||||
default:
|
||||
SMP2P_ERR("%s: Unknown event\n", __func__);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifier function passed into smp2p API for in bound entries.
|
||||
*
|
||||
* @self: Pointer to calling notifier block
|
||||
* @event: Event
|
||||
* @data: Event-specific data
|
||||
* @returns: 0
|
||||
*/
|
||||
static int smp2p_gpio_in_notify(struct notifier_block *self,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct smp2p_chip_dev *chip;
|
||||
|
||||
chip = container_of(self, struct smp2p_chip_dev, in_notifier);
|
||||
|
||||
switch (event) {
|
||||
case SMP2P_OPEN:
|
||||
chip->is_open = 1;
|
||||
SMP2P_GPIO("%s: Opened in '%s':%d\n", __func__,
|
||||
chip->name, chip->remote_pid);
|
||||
break;
|
||||
case SMP2P_ENTRY_UPDATE:
|
||||
msm_summary_irq_handler(chip, data);
|
||||
break;
|
||||
default:
|
||||
SMP2P_ERR("%s: Unknown event\n", __func__);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Device tree probe function.
|
||||
*
|
||||
* @pdev: Pointer to device tree data.
|
||||
* @returns: 0 on success; -ENODEV otherwise
|
||||
*
|
||||
* Called for each smp2pgpio entry in the device tree.
|
||||
*/
|
||||
static int smp2p_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node;
|
||||
char *key;
|
||||
struct smp2p_chip_dev *chip;
|
||||
const char *name_tmp;
|
||||
unsigned long flags;
|
||||
bool is_test_entry = false;
|
||||
int ret;
|
||||
|
||||
chip = kzalloc(sizeof(struct smp2p_chip_dev), GFP_KERNEL);
|
||||
if (!chip) {
|
||||
SMP2P_ERR("%s: out of memory\n", __func__);
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
spin_lock_init(&chip->irq_lock);
|
||||
|
||||
/* parse device tree */
|
||||
node = pdev->dev.of_node;
|
||||
key = "qcom,entry-name";
|
||||
ret = of_property_read_string(node, key, &name_tmp);
|
||||
if (ret) {
|
||||
SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
|
||||
goto fail;
|
||||
}
|
||||
strlcpy(chip->name, name_tmp, sizeof(chip->name));
|
||||
|
||||
key = "qcom,remote-pid";
|
||||
ret = of_property_read_u32(node, key, &chip->remote_pid);
|
||||
if (ret) {
|
||||
SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
key = "qcom,is-inbound";
|
||||
chip->is_inbound = of_property_read_bool(node, key);
|
||||
|
||||
/* create virtual GPIO controller */
|
||||
chip->gpio.label = chip->name;
|
||||
chip->gpio.dev = &pdev->dev;
|
||||
chip->gpio.owner = THIS_MODULE;
|
||||
chip->gpio.direction_input = smp2p_direction_input,
|
||||
chip->gpio.get = smp2p_get_value;
|
||||
chip->gpio.direction_output = smp2p_direction_output,
|
||||
chip->gpio.set = smp2p_set_value;
|
||||
chip->gpio.to_irq = smp2p_gpio_to_irq,
|
||||
chip->gpio.base = -1; /* use dynamic GPIO pin allocation */
|
||||
chip->gpio.ngpio = SMP2P_BITS_PER_ENTRY;
|
||||
ret = gpiochip_add(&chip->gpio);
|
||||
if (ret) {
|
||||
SMP2P_ERR("%s: unable to register GPIO '%s' ret %d\n",
|
||||
__func__, chip->name, ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test entries opened by GPIO Test conflict with loopback
|
||||
* support, so the test entries must be explicitly opened
|
||||
* in the unit test framework.
|
||||
*/
|
||||
if (strncmp("smp2p", chip->name, SMP2P_MAX_ENTRY_NAME) == 0)
|
||||
is_test_entry = true;
|
||||
|
||||
if (!chip->is_inbound) {
|
||||
chip->out_notifier.notifier_call = smp2p_gpio_out_notify;
|
||||
if (!is_test_entry) {
|
||||
ret = msm_smp2p_out_open(chip->remote_pid, chip->name,
|
||||
&chip->out_notifier,
|
||||
&chip->out_handle);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
chip->in_notifier.notifier_call = smp2p_gpio_in_notify;
|
||||
if (!is_test_entry) {
|
||||
ret = msm_smp2p_in_register(chip->remote_pid,
|
||||
chip->name,
|
||||
&chip->in_notifier);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
|
||||
list_add(&chip->entry_list, &smp2p_entry_list);
|
||||
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
|
||||
|
||||
/*
|
||||
* Create interrupt domain - note that chip can't be removed from the
|
||||
* interrupt domain, so chip cannot be deleted after this point.
|
||||
*/
|
||||
if (chip->is_inbound)
|
||||
smp2p_add_irq_domain(chip, node);
|
||||
else
|
||||
chip->irq_base = -1;
|
||||
|
||||
SMP2P_GPIO("%s: added %s%s entry '%s':%d gpio %d irq %d",
|
||||
__func__,
|
||||
is_test_entry ? "test " : "",
|
||||
chip->is_inbound ? "in" : "out",
|
||||
chip->name, chip->remote_pid,
|
||||
chip->gpio.base, chip->irq_base);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kfree(chip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_open_close - Opens or closes entry.
|
||||
*
|
||||
* @entry: Entry to open or close
|
||||
* @do_open: true = open port; false = close
|
||||
*/
|
||||
static void smp2p_gpio_open_close(struct smp2p_chip_dev *entry,
|
||||
bool do_open)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (do_open) {
|
||||
/* open entry */
|
||||
if (entry->is_inbound)
|
||||
ret = msm_smp2p_in_register(entry->remote_pid,
|
||||
entry->name, &entry->in_notifier);
|
||||
else
|
||||
ret = msm_smp2p_out_open(entry->remote_pid,
|
||||
entry->name, &entry->out_notifier,
|
||||
&entry->out_handle);
|
||||
SMP2P_GPIO("%s: opened %s '%s':%d ret %d\n",
|
||||
__func__,
|
||||
entry->is_inbound ? "in" : "out",
|
||||
entry->name, entry->remote_pid,
|
||||
ret);
|
||||
} else {
|
||||
/* close entry */
|
||||
if (entry->is_inbound)
|
||||
ret = msm_smp2p_in_unregister(entry->remote_pid,
|
||||
entry->name, &entry->in_notifier);
|
||||
else
|
||||
ret = msm_smp2p_out_close(&entry->out_handle);
|
||||
entry->is_open = false;
|
||||
SMP2P_GPIO("%s: closed %s '%s':%d ret %d\n",
|
||||
__func__,
|
||||
entry->is_inbound ? "in" : "out",
|
||||
entry->name, entry->remote_pid, ret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_open_test_entry - Opens or closes test entries for unit testing.
|
||||
*
|
||||
* @name: Name of the entry
|
||||
* @remote_pid: Remote processor ID
|
||||
* @do_open: true = open port; false = close
|
||||
*/
|
||||
void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open)
|
||||
{
|
||||
struct smp2p_chip_dev *entry;
|
||||
struct smp2p_chip_dev *start_entry;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
|
||||
if (list_empty(&smp2p_entry_list)) {
|
||||
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
|
||||
return;
|
||||
}
|
||||
start_entry = list_first_entry(&smp2p_entry_list,
|
||||
struct smp2p_chip_dev,
|
||||
entry_list);
|
||||
entry = start_entry;
|
||||
do {
|
||||
if (!strncmp(entry->name, name, SMP2P_MAX_ENTRY_NAME)
|
||||
&& entry->remote_pid == remote_pid) {
|
||||
/* found entry to change */
|
||||
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
|
||||
smp2p_gpio_open_close(entry, do_open);
|
||||
spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
|
||||
}
|
||||
list_rotate_left(&smp2p_entry_list);
|
||||
entry = list_first_entry(&smp2p_entry_list,
|
||||
struct smp2p_chip_dev,
|
||||
entry_list);
|
||||
} while (entry != start_entry);
|
||||
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
|
||||
}
|
||||
|
||||
static struct of_device_id msm_smp2p_match_table[] = {
|
||||
{.compatible = "qcom,smp2pgpio", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver smp2p_gpio_driver = {
|
||||
.probe = smp2p_gpio_probe,
|
||||
.driver = {
|
||||
.name = "smp2pgpio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = msm_smp2p_match_table,
|
||||
},
|
||||
};
|
||||
|
||||
static int smp2p_init(void)
|
||||
{
|
||||
INIT_LIST_HEAD(&smp2p_entry_list);
|
||||
return platform_driver_register(&smp2p_gpio_driver);
|
||||
}
|
||||
module_init(smp2p_init);
|
||||
|
||||
static void __exit smp2p_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&smp2p_gpio_driver);
|
||||
}
|
||||
module_exit(smp2p_exit);
|
||||
|
||||
MODULE_DESCRIPTION("SMP2P GPIO");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,624 @@
|
|||
/* arch/arm/mach-msm/smp2p_gpio_test.c
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include "smp2p_private.h"
|
||||
#include "smp2p_test_common.h"
|
||||
|
||||
/* Interrupt callback data */
|
||||
struct gpio_info {
|
||||
int gpio_base_id;
|
||||
int irq_base_id;
|
||||
|
||||
bool initialized;
|
||||
struct completion cb_completion;
|
||||
int cb_count;
|
||||
DECLARE_BITMAP(triggered_irqs, SMP2P_BITS_PER_ENTRY);
|
||||
};
|
||||
|
||||
/* GPIO Inbound/Outbound callback info */
|
||||
struct gpio_inout {
|
||||
struct gpio_info in;
|
||||
struct gpio_info out;
|
||||
};
|
||||
|
||||
static struct gpio_inout gpio_info[SMP2P_NUM_PROCS];
|
||||
|
||||
/**
|
||||
* Init/reset the callback data.
|
||||
*
|
||||
* @info: Pointer to callback data
|
||||
*/
|
||||
static void cb_data_reset(struct gpio_info *info)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
if (!info->initialized) {
|
||||
init_completion(&info->cb_completion);
|
||||
info->initialized = true;
|
||||
}
|
||||
info->cb_count = 0;
|
||||
|
||||
for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n)
|
||||
clear_bit(n, info->triggered_irqs);
|
||||
|
||||
INIT_COMPLETION(info->cb_completion);
|
||||
}
|
||||
|
||||
static int smp2p_gpio_test_probe(struct platform_device *pdev)
|
||||
{
|
||||
int id;
|
||||
int cnt;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct gpio_info *gpio_info_ptr = NULL;
|
||||
|
||||
/*
|
||||
* NOTE: This does a string-lookup of the GPIO pin name and doesn't
|
||||
* actually directly link to the SMP2P GPIO driver since all
|
||||
* GPIO/Interrupt access must be through standard
|
||||
* Linux GPIO / Interrupt APIs.
|
||||
*/
|
||||
if (strcmp("qcom,smp2pgpio_test_smp2p_1_in", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_MODEM_PROC].in;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_1_out", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_MODEM_PROC].out;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_2_in", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_AUDIO_PROC].in;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_2_out", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_AUDIO_PROC].out;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_4_in", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_WIRELESS_PROC].in;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_4_out", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_WIRELESS_PROC].out;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_7_in", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
|
||||
} else if (strcmp("qcom,smp2pgpio_test_smp2p_7_out", node->name) == 0) {
|
||||
gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].out;
|
||||
} else {
|
||||
pr_err("%s: unable to match device type '%s'\n",
|
||||
__func__, node->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* retrieve the GPIO and interrupt ID's */
|
||||
cnt = of_gpio_count(node);
|
||||
if (cnt && gpio_info_ptr) {
|
||||
/*
|
||||
* Instead of looping through all 32-bits, we can just get the
|
||||
* first pin to get the base IDs. This saves on the verbosity
|
||||
* of the device tree nodes as well.
|
||||
*/
|
||||
id = of_get_gpio(node, 0);
|
||||
gpio_info_ptr->gpio_base_id = id;
|
||||
gpio_info_ptr->irq_base_id = gpio_to_irq(id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Instead of match table and device driver, you may be able to just
|
||||
* call of_find_compatible_node() in your init function.
|
||||
*/
|
||||
static struct of_device_id msm_smp2p_match_table[] = {
|
||||
/* modem */
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_1_out", },
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_1_in", },
|
||||
|
||||
/* audio (adsp) */
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_2_out", },
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_2_in", },
|
||||
|
||||
/* wcnss */
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_4_out", },
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_4_in", },
|
||||
|
||||
/* mock loopback */
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_7_out", },
|
||||
{.compatible = "qcom,smp2pgpio_test_smp2p_7_in", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver smp2p_gpio_driver = {
|
||||
.probe = smp2p_gpio_test_probe,
|
||||
.driver = {
|
||||
.name = "smp2pgpio_test",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = msm_smp2p_match_table,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* smp2p_ut_local_gpio_out - Verify outbound functionality.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*/
|
||||
static void smp2p_ut_local_gpio_out(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].out;
|
||||
int ret;
|
||||
int id;
|
||||
struct msm_smp2p_remote_mock *mock;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
do {
|
||||
/* initialize mock edge */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
mock = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(mock, !=, NULL);
|
||||
|
||||
mock->rx_interrupt_count = 0;
|
||||
memset(&mock->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
|
||||
SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
|
||||
0, 1);
|
||||
strlcpy(mock->remote_item.entries[0].name, "smp2p",
|
||||
SMP2P_MAX_ENTRY_NAME);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
mock->remote_item.header.valid_total_ent, 1);
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
mock->tx_interrupt();
|
||||
|
||||
/* open GPIO entry */
|
||||
smp2p_gpio_open_test_entry("smp2p",
|
||||
SMP2P_REMOTE_MOCK_PROC, true);
|
||||
|
||||
/* verify set/get functions */
|
||||
UT_ASSERT_INT(0, <, cb_info->gpio_base_id);
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
int pin = cb_info->gpio_base_id + id;
|
||||
|
||||
mock->rx_interrupt_count = 0;
|
||||
gpio_set_value(pin, 1);
|
||||
UT_ASSERT_INT(1, ==, mock->rx_interrupt_count);
|
||||
UT_ASSERT_INT(1, ==, gpio_get_value(pin));
|
||||
|
||||
gpio_set_value(pin, 0);
|
||||
UT_ASSERT_INT(2, ==, mock->rx_interrupt_count);
|
||||
UT_ASSERT_INT(0, ==, gpio_get_value(pin));
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
|
||||
smp2p_gpio_open_test_entry("smp2p",
|
||||
SMP2P_REMOTE_MOCK_PROC, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_irq - Interrupt handler for inbound entries.
|
||||
*
|
||||
* @irq: Virtual IRQ being triggered
|
||||
* @data: Cookie data (struct gpio_info * in this case)
|
||||
* @returns: Number of bytes written
|
||||
*/
|
||||
static irqreturn_t smp2p_gpio_irq(int irq, void *data)
|
||||
{
|
||||
struct gpio_info *gpio_ptr = (struct gpio_info *)data;
|
||||
int offset;
|
||||
|
||||
if (!gpio_ptr) {
|
||||
pr_err("%s: gpio_ptr is NULL for irq %d\n", __func__, irq);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
offset = irq - gpio_ptr->irq_base_id;
|
||||
if (offset >= 0 && offset < SMP2P_BITS_PER_ENTRY)
|
||||
set_bit(offset, gpio_ptr->triggered_irqs);
|
||||
else
|
||||
pr_err("%s: invalid irq offset base %d; irq %d\n",
|
||||
__func__, gpio_ptr->irq_base_id, irq);
|
||||
|
||||
++gpio_ptr->cb_count;
|
||||
complete(&gpio_ptr->cb_completion);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_local_gpio_in - Verify inbound functionality.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*/
|
||||
static void smp2p_ut_local_gpio_in(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
|
||||
int id;
|
||||
int ret;
|
||||
int virq;
|
||||
struct msm_smp2p_remote_mock *mock;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
|
||||
cb_data_reset(cb_info);
|
||||
do {
|
||||
/* initialize mock edge */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
mock = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(mock, !=, NULL);
|
||||
|
||||
mock->rx_interrupt_count = 0;
|
||||
memset(&mock->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
|
||||
SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
|
||||
0, 1);
|
||||
strlcpy(mock->remote_item.entries[0].name, "smp2p",
|
||||
SMP2P_MAX_ENTRY_NAME);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
mock->remote_item.header.valid_total_ent, 1);
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
mock->tx_interrupt();
|
||||
|
||||
smp2p_gpio_open_test_entry("smp2p",
|
||||
SMP2P_REMOTE_MOCK_PROC, true);
|
||||
|
||||
/* verify set/get functions locally */
|
||||
UT_ASSERT_INT(0, <, cb_info->gpio_base_id);
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
int pin;
|
||||
int current_value;
|
||||
|
||||
/* verify pin value cannot be set */
|
||||
pin = cb_info->gpio_base_id + id;
|
||||
current_value = gpio_get_value(pin);
|
||||
|
||||
gpio_set_value(pin, 0);
|
||||
UT_ASSERT_INT(current_value, ==, gpio_get_value(pin));
|
||||
gpio_set_value(pin, 1);
|
||||
UT_ASSERT_INT(current_value, ==, gpio_get_value(pin));
|
||||
|
||||
/* verify no interrupts */
|
||||
UT_ASSERT_INT(0, ==, cb_info->cb_count);
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* register for interrupts */
|
||||
UT_ASSERT_INT(0, <, cb_info->irq_base_id);
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
virq = cb_info->irq_base_id + id;
|
||||
UT_ASSERT_INT(0, >, (unsigned int)irq_to_desc(virq));
|
||||
ret = request_irq(virq,
|
||||
smp2p_gpio_irq, IRQF_TRIGGER_RISING,
|
||||
"smp2p_test", cb_info);
|
||||
UT_ASSERT_INT(0, ==, ret);
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* verify both rising and falling edge interrupts */
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
virq = cb_info->irq_base_id + id;
|
||||
irq_set_irq_type(virq, IRQ_TYPE_EDGE_BOTH);
|
||||
cb_data_reset(cb_info);
|
||||
|
||||
/* verify rising-edge interrupt */
|
||||
mock->remote_item.entries[0].entry = 1 << id;
|
||||
mock->tx_interrupt();
|
||||
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
|
||||
UT_ASSERT_INT(0, <,
|
||||
test_bit(id, cb_info->triggered_irqs));
|
||||
test_bit(id, cb_info->triggered_irqs);
|
||||
|
||||
/* verify falling-edge interrupt */
|
||||
mock->remote_item.entries[0].entry = 0;
|
||||
mock->tx_interrupt();
|
||||
UT_ASSERT_INT(cb_info->cb_count, ==, 2);
|
||||
UT_ASSERT_INT(0, <,
|
||||
test_bit(id, cb_info->triggered_irqs));
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* verify rising-edge interrupts */
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
virq = cb_info->irq_base_id + id;
|
||||
irq_set_irq_type(virq, IRQ_TYPE_EDGE_RISING);
|
||||
cb_data_reset(cb_info);
|
||||
|
||||
/* verify only rising-edge interrupt is triggered */
|
||||
mock->remote_item.entries[0].entry = 1 << id;
|
||||
mock->tx_interrupt();
|
||||
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
|
||||
UT_ASSERT_INT(0, <,
|
||||
test_bit(id, cb_info->triggered_irqs));
|
||||
test_bit(id, cb_info->triggered_irqs);
|
||||
|
||||
mock->remote_item.entries[0].entry = 0;
|
||||
mock->tx_interrupt();
|
||||
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
|
||||
UT_ASSERT_INT(0, <,
|
||||
test_bit(id, cb_info->triggered_irqs));
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* verify falling-edge interrupts */
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
virq = cb_info->irq_base_id + id;
|
||||
irq_set_irq_type(virq, IRQ_TYPE_EDGE_FALLING);
|
||||
cb_data_reset(cb_info);
|
||||
|
||||
/* verify only rising-edge interrupt is triggered */
|
||||
mock->remote_item.entries[0].entry = 1 << id;
|
||||
mock->tx_interrupt();
|
||||
UT_ASSERT_INT(cb_info->cb_count, ==, 0);
|
||||
UT_ASSERT_INT(0, ==,
|
||||
test_bit(id, cb_info->triggered_irqs));
|
||||
|
||||
mock->remote_item.entries[0].entry = 0;
|
||||
mock->tx_interrupt();
|
||||
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
|
||||
UT_ASSERT_INT(0, <,
|
||||
test_bit(id, cb_info->triggered_irqs));
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
|
||||
/* unregister for interrupts */
|
||||
if (cb_info->irq_base_id) {
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
|
||||
free_irq(cb_info->irq_base_id + id, cb_info);
|
||||
}
|
||||
|
||||
smp2p_gpio_open_test_entry("smp2p",
|
||||
SMP2P_REMOTE_MOCK_PROC, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_write_bits - writes value to each GPIO pin specified in mask.
|
||||
*
|
||||
* @gpio: gpio test structure
|
||||
* @mask: 1 = write gpio_value to this GPIO pin
|
||||
* @gpio_value: value to write to GPIO pin
|
||||
*/
|
||||
static void smp2p_gpio_write_bits(struct gpio_info *gpio, uint32_t mask,
|
||||
int gpio_value)
|
||||
{
|
||||
int n;
|
||||
|
||||
for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n) {
|
||||
if (mask & 0x1)
|
||||
gpio_set_value(gpio->gpio_base_id + n, gpio_value);
|
||||
mask >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void smp2p_gpio_set_bits(struct gpio_info *gpio, uint32_t mask)
|
||||
{
|
||||
smp2p_gpio_write_bits(gpio, mask, 1);
|
||||
}
|
||||
|
||||
static void smp2p_gpio_clr_bits(struct gpio_info *gpio, uint32_t mask)
|
||||
{
|
||||
smp2p_gpio_write_bits(gpio, mask, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_gpio_get_value - reads entire 32-bits of GPIO
|
||||
*
|
||||
* @gpio: gpio structure
|
||||
* @returns: 32 bit value of GPIO pins
|
||||
*/
|
||||
static uint32_t smp2p_gpio_get_value(struct gpio_info *gpio)
|
||||
{
|
||||
int n;
|
||||
uint32_t value = 0;
|
||||
|
||||
for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n) {
|
||||
if (gpio_get_value(gpio->gpio_base_id + n))
|
||||
value |= 1 << n;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_inout_core - Verify inbound/outbound functionality.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
* @remote_pid: Remote processor to test
|
||||
* @name: Name of the test for reporting
|
||||
*
|
||||
* This test verifies inbound/outbound functionality for the remote processor.
|
||||
*/
|
||||
static void smp2p_ut_remote_inout_core(struct seq_file *s, int remote_pid,
|
||||
const char *name)
|
||||
{
|
||||
int failed = 0;
|
||||
uint32_t request;
|
||||
uint32_t response;
|
||||
struct gpio_info *cb_in;
|
||||
struct gpio_info *cb_out;
|
||||
int id;
|
||||
int ret;
|
||||
|
||||
seq_printf(s, "Running %s for '%s' remote pid %d\n",
|
||||
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
|
||||
|
||||
cb_in = &gpio_info[remote_pid].in;
|
||||
cb_out = &gpio_info[remote_pid].out;
|
||||
cb_data_reset(cb_in);
|
||||
cb_data_reset(cb_out);
|
||||
do {
|
||||
/* open test entries */
|
||||
msm_smp2p_deinit_rmt_lpb_proc(remote_pid);
|
||||
smp2p_gpio_open_test_entry("smp2p", remote_pid, true);
|
||||
|
||||
/* register for interrupts */
|
||||
UT_ASSERT_INT(0, <, cb_in->gpio_base_id);
|
||||
UT_ASSERT_INT(0, <, cb_in->irq_base_id);
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
|
||||
int virq = cb_in->irq_base_id + id;
|
||||
UT_ASSERT_INT(0, >, (unsigned int)irq_to_desc(virq));
|
||||
ret = request_irq(virq,
|
||||
smp2p_gpio_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
"smp2p_test", cb_in);
|
||||
UT_ASSERT_INT(0, ==, ret);
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* write echo of data value 0 */
|
||||
UT_ASSERT_INT(0, <, cb_out->gpio_base_id);
|
||||
request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(request, 1);
|
||||
SMP2P_SET_RMT_CMD(request, SMP2P_LB_CMD_ECHO);
|
||||
SMP2P_SET_RMT_DATA(request, 0x0);
|
||||
|
||||
smp2p_gpio_set_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
|
||||
smp2p_gpio_clr_bits(cb_out, ~SMP2P_RMT_IGNORE_MASK);
|
||||
smp2p_gpio_set_bits(cb_out, request);
|
||||
|
||||
UT_ASSERT_INT(cb_in->cb_count, ==, 0);
|
||||
smp2p_gpio_clr_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
|
||||
|
||||
/* verify response */
|
||||
do {
|
||||
/* wait for up to 32 changes */
|
||||
if (wait_for_completion_timeout(
|
||||
&cb_in->cb_completion, HZ / 2) == 0)
|
||||
break;
|
||||
INIT_COMPLETION(cb_in->cb_completion);
|
||||
} while (cb_in->cb_count < 32);
|
||||
UT_ASSERT_INT(cb_in->cb_count, >, 0);
|
||||
response = smp2p_gpio_get_value(cb_in);
|
||||
SMP2P_SET_RMT_CMD_TYPE(request, 0);
|
||||
UT_ASSERT_HEX(request, ==, response);
|
||||
|
||||
/* write echo of data value of all 1's */
|
||||
request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(request, 1);
|
||||
SMP2P_SET_RMT_CMD(request, SMP2P_LB_CMD_ECHO);
|
||||
SMP2P_SET_RMT_DATA(request, ~0);
|
||||
|
||||
smp2p_gpio_set_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
|
||||
cb_data_reset(cb_in);
|
||||
smp2p_gpio_clr_bits(cb_out, ~SMP2P_RMT_IGNORE_MASK);
|
||||
smp2p_gpio_set_bits(cb_out, request);
|
||||
|
||||
UT_ASSERT_INT(cb_in->cb_count, ==, 0);
|
||||
smp2p_gpio_clr_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
|
||||
|
||||
/* verify response including 24 interrupts */
|
||||
do {
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in->cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
INIT_COMPLETION(cb_in->cb_completion);
|
||||
} while (cb_in->cb_count < 24);
|
||||
response = smp2p_gpio_get_value(cb_in);
|
||||
SMP2P_SET_RMT_CMD_TYPE(request, 0);
|
||||
UT_ASSERT_HEX(request, ==, response);
|
||||
UT_ASSERT_INT(24, ==, cb_in->cb_count);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", name);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
|
||||
/* unregister for interrupts */
|
||||
if (cb_in->irq_base_id) {
|
||||
for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
|
||||
free_irq(cb_in->irq_base_id + id, cb_in);
|
||||
}
|
||||
|
||||
smp2p_gpio_open_test_entry("smp2p", remote_pid, false);
|
||||
msm_smp2p_init_rmt_lpb_proc(remote_pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_inout - Verify inbound/outbound functionality for all.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test verifies inbound and outbound functionality for all
|
||||
* configured remote processor.
|
||||
*/
|
||||
static void smp2p_ut_remote_inout(struct seq_file *s)
|
||||
{
|
||||
struct smp2p_interrupt_config *int_cfg;
|
||||
int pid;
|
||||
|
||||
int_cfg = smp2p_get_interrupt_config();
|
||||
if (!int_cfg) {
|
||||
seq_printf(s, "Remote processor config unavailable\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
|
||||
if (!int_cfg[pid].is_configured)
|
||||
continue;
|
||||
|
||||
smp2p_ut_remote_inout_core(s, pid, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init smp2p_debugfs_init(void)
|
||||
{
|
||||
/* register GPIO pins */
|
||||
(void)platform_driver_register(&smp2p_gpio_driver);
|
||||
|
||||
/*
|
||||
* Add Unit Test entries.
|
||||
*
|
||||
* The idea with unit tests is that you can run all of them
|
||||
* from ADB shell by doing:
|
||||
* adb shell
|
||||
* cat ut*
|
||||
*
|
||||
* And if particular tests fail, you can then repeatedly run the
|
||||
* failing tests as you debug and resolve the failing test.
|
||||
*/
|
||||
smp2p_debug_create("ut_local_gpio_out", smp2p_ut_local_gpio_out);
|
||||
smp2p_debug_create("ut_local_gpio_in", smp2p_ut_local_gpio_in);
|
||||
smp2p_debug_create("ut_remote_gpio_inout", smp2p_ut_remote_inout);
|
||||
return 0;
|
||||
}
|
||||
late_initcall(smp2p_debugfs_init);
|
|
@ -0,0 +1,440 @@
|
|||
/* arch/arm/mach-msm/smp2p_loopback.c
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/debugfs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/termios.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/remote_spinlock.h>
|
||||
#include "smd_private.h"
|
||||
#include "smp2p_private.h"
|
||||
|
||||
/**
|
||||
* struct smp2p_loopback_ctx - Representation of remote loopback object.
|
||||
*
|
||||
* @proc_id: Processor id of the processor that sends the loopback commands.
|
||||
* @out: Handle to the smem entry structure for providing the response.
|
||||
* @out_nb: Notifies the opening of local entry.
|
||||
* @out_is_active: Outbound entry events should be processed.
|
||||
* @in_nb: Notifies changes in the remote entry.
|
||||
* @in_is_active: Inbound entry events should be processed.
|
||||
* @rmt_lpb_work: Work item that handles the incoming loopback commands.
|
||||
* @rmt_cmd: Structure that holds the current and previous value of the entry.
|
||||
*/
|
||||
struct smp2p_loopback_ctx {
|
||||
int proc_id;
|
||||
struct msm_smp2p_out *out;
|
||||
struct notifier_block out_nb;
|
||||
bool out_is_active;
|
||||
struct notifier_block in_nb;
|
||||
bool in_is_active;
|
||||
struct work_struct rmt_lpb_work;
|
||||
struct msm_smp2p_update_notif rmt_cmd;
|
||||
};
|
||||
|
||||
static struct smp2p_loopback_ctx remote_loopback[SMP2P_NUM_PROCS];
|
||||
static struct msm_smp2p_remote_mock remote_mock;
|
||||
|
||||
/**
|
||||
* remote_spinlock_test - Handles remote spinlock test.
|
||||
*
|
||||
* @ctx: Loopback context
|
||||
*/
|
||||
static void remote_spinlock_test(struct smp2p_loopback_ctx *ctx)
|
||||
{
|
||||
uint32_t test_request;
|
||||
uint32_t test_response;
|
||||
unsigned long flags;
|
||||
int n;
|
||||
unsigned lock_count = 0;
|
||||
remote_spinlock_t *smem_spinlock;
|
||||
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
|
||||
smem_spinlock = smem_get_remote_spinlock();
|
||||
if (!smem_spinlock) {
|
||||
pr_err("%s: unable to get remote spinlock\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
remote_spin_lock_irqsave(smem_spinlock, flags);
|
||||
++lock_count;
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_LOCKED);
|
||||
(void)msm_smp2p_out_write(ctx->out, test_request);
|
||||
|
||||
for (n = 0; n < 10000; ++n) {
|
||||
(void)msm_smp2p_in_read(ctx->proc_id,
|
||||
"smp2p", &test_response);
|
||||
test_response = SMP2P_GET_RMT_CMD(test_response);
|
||||
|
||||
if (test_response == SMP2P_LB_CMD_RSPIN_END)
|
||||
break;
|
||||
|
||||
if (test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED)
|
||||
SMP2P_ERR("%s: invalid spinlock command %x\n",
|
||||
__func__, test_response);
|
||||
}
|
||||
|
||||
if (test_response == SMP2P_LB_CMD_RSPIN_END) {
|
||||
SMP2P_SET_RMT_CMD_TYPE_RESP(test_request);
|
||||
SMP2P_SET_RMT_CMD(test_request,
|
||||
SMP2P_LB_CMD_RSPIN_END);
|
||||
SMP2P_SET_RMT_DATA(test_request, lock_count);
|
||||
(void)msm_smp2p_out_write(ctx->out, test_request);
|
||||
break;
|
||||
}
|
||||
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_UNLOCKED);
|
||||
(void)msm_smp2p_out_write(ctx->out, test_request);
|
||||
remote_spin_unlock_irqrestore(smem_spinlock, flags);
|
||||
}
|
||||
remote_spin_unlock_irqrestore(smem_spinlock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_rmt_lpb_worker - Handles incoming remote loopback commands.
|
||||
*
|
||||
* @work: Work Item scheduled to handle the incoming commands.
|
||||
*/
|
||||
static void smp2p_rmt_lpb_worker(struct work_struct *work)
|
||||
{
|
||||
struct smp2p_loopback_ctx *ctx;
|
||||
int lpb_cmd;
|
||||
int lpb_cmd_type;
|
||||
int lpb_data;
|
||||
|
||||
ctx = container_of(work, struct smp2p_loopback_ctx, rmt_lpb_work);
|
||||
|
||||
if (!ctx->in_is_active || !ctx->out_is_active)
|
||||
return;
|
||||
|
||||
if (ctx->rmt_cmd.previous_value == ctx->rmt_cmd.current_value)
|
||||
return;
|
||||
|
||||
lpb_cmd_type = SMP2P_GET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value);
|
||||
lpb_cmd = SMP2P_GET_RMT_CMD(ctx->rmt_cmd.current_value);
|
||||
lpb_data = SMP2P_GET_RMT_DATA(ctx->rmt_cmd.current_value);
|
||||
|
||||
if (lpb_cmd & SMP2P_RLPB_IGNORE)
|
||||
return;
|
||||
|
||||
switch (lpb_cmd) {
|
||||
case SMP2P_LB_CMD_NOOP:
|
||||
/* Do nothing */
|
||||
break;
|
||||
|
||||
case SMP2P_LB_CMD_ECHO:
|
||||
SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0);
|
||||
SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value,
|
||||
lpb_data);
|
||||
(void)msm_smp2p_out_write(ctx->out,
|
||||
ctx->rmt_cmd.current_value);
|
||||
break;
|
||||
|
||||
case SMP2P_LB_CMD_CLEARALL:
|
||||
ctx->rmt_cmd.current_value = 0;
|
||||
(void)msm_smp2p_out_write(ctx->out,
|
||||
ctx->rmt_cmd.current_value);
|
||||
break;
|
||||
|
||||
case SMP2P_LB_CMD_PINGPONG:
|
||||
SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0);
|
||||
if (lpb_data) {
|
||||
lpb_data--;
|
||||
SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value,
|
||||
lpb_data);
|
||||
(void)msm_smp2p_out_write(ctx->out,
|
||||
ctx->rmt_cmd.current_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case SMP2P_LB_CMD_RSPIN_START:
|
||||
remote_spinlock_test(ctx);
|
||||
break;
|
||||
|
||||
case SMP2P_LB_CMD_RSPIN_LOCKED:
|
||||
case SMP2P_LB_CMD_RSPIN_UNLOCKED:
|
||||
case SMP2P_LB_CMD_RSPIN_END:
|
||||
/* not used for remote spinlock test */
|
||||
break;
|
||||
|
||||
default:
|
||||
SMP2P_DBG("%s: Unknown loopback command %x\n",
|
||||
__func__, lpb_cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_rmt_in_edge_notify - Schedules a work item to handle the commands.
|
||||
*
|
||||
* @nb: Notifier block, this is called when the value in remote entry changes.
|
||||
* @event: Takes value SMP2P_ENTRY_UPDATE or SMP2P_OPEN based on the event.
|
||||
* @data: Consists of previous and current value in case of entry update.
|
||||
* @returns: 0 for success (return value required for notifier chains).
|
||||
*/
|
||||
static int smp2p_rmt_in_edge_notify(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct smp2p_loopback_ctx *ctx;
|
||||
|
||||
if (!(event == SMP2P_ENTRY_UPDATE || event == SMP2P_OPEN))
|
||||
return 0;
|
||||
|
||||
ctx = container_of(nb, struct smp2p_loopback_ctx, in_nb);
|
||||
if (data && ctx->in_is_active) {
|
||||
ctx->rmt_cmd =
|
||||
*(struct msm_smp2p_update_notif *)data;
|
||||
schedule_work(&ctx->rmt_lpb_work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_rmt_out_edge_notify - Notifies on the opening of the outbound entry.
|
||||
*
|
||||
* @nb: Notifier block, this is called when the local entry is open.
|
||||
* @event: Takes on value SMP2P_OPEN when the local entry is open.
|
||||
* @data: Consist of current value of the remote entry, if entry is open.
|
||||
* @returns: 0 for success (return value required for notifier chains).
|
||||
*/
|
||||
static int smp2p_rmt_out_edge_notify(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct smp2p_loopback_ctx *ctx;
|
||||
|
||||
ctx = container_of(nb, struct smp2p_loopback_ctx, out_nb);
|
||||
if (event == SMP2P_OPEN)
|
||||
SMP2P_DBG("%s: 'smp2p':%d opened\n", __func__,
|
||||
ctx->proc_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* msm_smp2p_init_rmt_lpb - Initializes the remote loopback object.
|
||||
*
|
||||
* @ctx: Pointer to remote loopback object that needs to be initialized.
|
||||
* @pid: Processor id of the processor that is sending the commands.
|
||||
* @entry: Name of the entry that needs to be opened locally.
|
||||
* @returns: 0 on success, standard Linux error code otherwise.
|
||||
*/
|
||||
static int msm_smp2p_init_rmt_lpb(struct smp2p_loopback_ctx *ctx,
|
||||
int pid, const char *entry)
|
||||
{
|
||||
int ret = 0;
|
||||
int tmp;
|
||||
|
||||
if (!ctx || !entry || pid > SMP2P_NUM_PROCS)
|
||||
return -EINVAL;
|
||||
|
||||
ctx->in_nb.notifier_call = smp2p_rmt_in_edge_notify;
|
||||
ctx->out_nb.notifier_call = smp2p_rmt_out_edge_notify;
|
||||
ctx->proc_id = pid;
|
||||
ctx->in_is_active = true;
|
||||
ctx->out_is_active = true;
|
||||
tmp = msm_smp2p_out_open(pid, entry, &ctx->out_nb,
|
||||
&ctx->out);
|
||||
if (tmp) {
|
||||
SMP2P_ERR("%s: open failed outbound entry '%s':%d - ret %d\n",
|
||||
__func__, entry, pid, tmp);
|
||||
ret = tmp;
|
||||
}
|
||||
|
||||
tmp = msm_smp2p_in_register(ctx->proc_id,
|
||||
SMP2P_RLPB_ENTRY_NAME,
|
||||
&ctx->in_nb);
|
||||
if (tmp) {
|
||||
SMP2P_ERR("%s: unable to open inbound entry '%s':%d - ret %d\n",
|
||||
__func__, entry, pid, tmp);
|
||||
ret = tmp;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* msm_smp2p_init_rmt_lpb_proc - Wrapper over msm_smp2p_init_rmt_lpb
|
||||
*
|
||||
* @remote_pid: Processor ID of the processor that sends loopback command.
|
||||
* @returns: Pointer to outbound entry handle.
|
||||
*/
|
||||
void *msm_smp2p_init_rmt_lpb_proc(int remote_pid)
|
||||
{
|
||||
int tmp;
|
||||
void *ret = NULL;
|
||||
|
||||
tmp = msm_smp2p_init_rmt_lpb(&remote_loopback[remote_pid],
|
||||
remote_pid, SMP2P_RLPB_ENTRY_NAME);
|
||||
if (!tmp)
|
||||
ret = remote_loopback[remote_pid].out;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_smp2p_init_rmt_lpb_proc);
|
||||
|
||||
/**
|
||||
* msm_smp2p_deinit_rmt_lpb_proc - Unregister support for remote processor.
|
||||
*
|
||||
* @remote_pid: Processor ID of the remote system.
|
||||
* @returns: 0 on success, standard Linux error code otherwise.
|
||||
*
|
||||
* Unregister loopback support for remote processor.
|
||||
*/
|
||||
int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid)
|
||||
{
|
||||
int ret = 0;
|
||||
int tmp;
|
||||
struct smp2p_loopback_ctx *ctx;
|
||||
|
||||
if (remote_pid >= SMP2P_NUM_PROCS)
|
||||
return -EINVAL;
|
||||
|
||||
ctx = &remote_loopback[remote_pid];
|
||||
|
||||
/* abort any pending notifications */
|
||||
remote_loopback[remote_pid].out_is_active = false;
|
||||
remote_loopback[remote_pid].in_is_active = false;
|
||||
flush_work(&ctx->rmt_lpb_work);
|
||||
|
||||
/* unregister entries */
|
||||
tmp = msm_smp2p_out_close(&remote_loopback[remote_pid].out);
|
||||
remote_loopback[remote_pid].out = NULL;
|
||||
if (tmp) {
|
||||
SMP2P_ERR("%s: outbound 'smp2p':%d close failed %d\n",
|
||||
__func__, remote_pid, tmp);
|
||||
ret = tmp;
|
||||
}
|
||||
|
||||
tmp = msm_smp2p_in_unregister(remote_pid,
|
||||
SMP2P_RLPB_ENTRY_NAME, &remote_loopback[remote_pid].in_nb);
|
||||
if (tmp) {
|
||||
SMP2P_ERR("%s: inbound 'smp2p':%d close failed %d\n",
|
||||
__func__, remote_pid, tmp);
|
||||
ret = tmp;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_smp2p_deinit_rmt_lpb_proc);
|
||||
|
||||
/**
|
||||
* msm_smp2p_set_remote_mock_exists - Sets the remote mock configuration.
|
||||
*
|
||||
* @item_exists: true = Remote mock SMEM item exists
|
||||
*
|
||||
* This is used in the testing environment to simulate the existence of the
|
||||
* remote smem item in order to test the negotiation algorithm.
|
||||
*/
|
||||
void msm_smp2p_set_remote_mock_exists(bool item_exists)
|
||||
{
|
||||
remote_mock.item_exists = item_exists;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_smp2p_set_remote_mock_exists);
|
||||
|
||||
/**
|
||||
* msm_smp2p_get_remote_mock - Get remote mock object.
|
||||
*
|
||||
* @returns: Point to the remote mock object.
|
||||
*/
|
||||
void *msm_smp2p_get_remote_mock(void)
|
||||
{
|
||||
return &remote_mock;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_smp2p_get_remote_mock);
|
||||
|
||||
/**
|
||||
* msm_smp2p_get_remote_mock_smem_item - Returns a pointer to remote item.
|
||||
*
|
||||
* @size: Size of item.
|
||||
* @returns: Pointer to mock remote smem item.
|
||||
*/
|
||||
void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size)
|
||||
{
|
||||
void *ptr = NULL;
|
||||
if (remote_mock.item_exists) {
|
||||
*size = sizeof(remote_mock.remote_item);
|
||||
ptr = &(remote_mock.remote_item);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_smp2p_get_remote_mock_smem_item);
|
||||
|
||||
/**
|
||||
* smp2p_remote_mock_rx_interrupt - Triggers receive interrupt for mock proc.
|
||||
*
|
||||
* @returns: 0 for success
|
||||
*
|
||||
* This function simulates the receiving of interrupt by the mock remote
|
||||
* processor in a testing environment.
|
||||
*/
|
||||
int smp2p_remote_mock_rx_interrupt(void)
|
||||
{
|
||||
remote_mock.rx_interrupt_count++;
|
||||
if (remote_mock.initialized)
|
||||
complete(&remote_mock.cb_completion);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(smp2p_remote_mock_rx_interrupt);
|
||||
|
||||
/**
|
||||
* smp2p_remote_mock_tx_interrupt - Calls the SMP2P interrupt handler.
|
||||
*
|
||||
* This function calls the interrupt handler of the Apps processor to simulate
|
||||
* receiving interrupts from a remote processor.
|
||||
*/
|
||||
static void smp2p_remote_mock_tx_interrupt(void)
|
||||
{
|
||||
msm_smp2p_interrupt_handler(SMP2P_REMOTE_MOCK_PROC);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_remote_mock_init - Initialize the remote mock and loopback objects.
|
||||
*
|
||||
* @returns: 0 for success
|
||||
*/
|
||||
static int __init smp2p_remote_mock_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
smp2p_init_header(&remote_mock.remote_item.header,
|
||||
SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
|
||||
0, 0);
|
||||
remote_mock.rx_interrupt_count = 0;
|
||||
remote_mock.rx_interrupt = smp2p_remote_mock_rx_interrupt;
|
||||
remote_mock.tx_interrupt = smp2p_remote_mock_tx_interrupt;
|
||||
remote_mock.item_exists = false;
|
||||
init_completion(&remote_mock.cb_completion);
|
||||
remote_mock.initialized = true;
|
||||
|
||||
for (i = 0; i < SMP2P_NUM_PROCS; i++) {
|
||||
INIT_WORK(&(remote_loopback[i].rmt_lpb_work),
|
||||
smp2p_rmt_lpb_worker);
|
||||
if (i == SMP2P_REMOTE_MOCK_PROC)
|
||||
/* do not register loopback for remote mock proc */
|
||||
continue;
|
||||
|
||||
msm_smp2p_init_rmt_lpb(&remote_loopback[i],
|
||||
i, SMP2P_RLPB_ENTRY_NAME);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
module_init(smp2p_remote_mock_init);
|
|
@ -0,0 +1,226 @@
|
|||
/* arch/arm/mach-msm/smp2p_private.h
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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.
|
||||
*/
|
||||
#ifndef _ARCH_ARM_MACH_MSM_MSM_SMP2P_PRIVATE_H_
|
||||
#define _ARCH_ARM_MACH_MSM_MSM_SMP2P_PRIVATE_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <mach/msm_ipc_logging.h>
|
||||
#include "smp2p_private_api.h"
|
||||
|
||||
#define SMP2P_MAX_ENTRY 16
|
||||
#define SMP2P_LOCAL_VERSION 1
|
||||
#define SMP2P_LOCAL_FEATURE 0x0
|
||||
|
||||
/* SMEM Item Header Macros */
|
||||
#define SMP2P_MAGIC 0x504D5324
|
||||
#define SMP2P_LOCAL_PID_MASK 0x0000ffff
|
||||
#define SMP2P_LOCAL_PID_BIT 0
|
||||
#define SMP2P_REMOTE_PID_MASK 0xffff0000
|
||||
#define SMP2P_REMOTE_PID_BIT 16
|
||||
#define SMP2P_VERSION_MASK 0x000000ff
|
||||
#define SMP2P_VERSION_BIT 0
|
||||
#define SMP2P_FEATURE_MASK 0xffffff00
|
||||
#define SMP2P_FEATURE_BIT 8
|
||||
#define SMP2P_ENT_TOTAL_MASK 0x0000ffff
|
||||
#define SMP2P_ENT_TOTAL_BIT 0
|
||||
#define SMP2P_ENT_VALID_MASK 0xffff0000
|
||||
#define SMP2P_ENT_VALID_BIT 16
|
||||
|
||||
#define SMP2P_GET_BITS(hdr_val, mask, bit) \
|
||||
(((hdr_val) & (mask)) >> (bit))
|
||||
#define SMP2P_SET_BITS(hdr_val, mask, bit, new_value) \
|
||||
do {\
|
||||
hdr_val = (hdr_val & ~(mask)) \
|
||||
| (((new_value) << (bit)) & (mask)); \
|
||||
} while (0)
|
||||
|
||||
#define SMP2P_GET_LOCAL_PID(hdr) \
|
||||
SMP2P_GET_BITS(hdr, SMP2P_LOCAL_PID_MASK, SMP2P_LOCAL_PID_BIT)
|
||||
#define SMP2P_SET_LOCAL_PID(hdr, pid) \
|
||||
SMP2P_SET_BITS(hdr, SMP2P_LOCAL_PID_MASK, SMP2P_LOCAL_PID_BIT, pid)
|
||||
|
||||
#define SMP2P_GET_REMOTE_PID(hdr) \
|
||||
SMP2P_GET_BITS(hdr, SMP2P_REMOTE_PID_MASK, SMP2P_REMOTE_PID_BIT)
|
||||
#define SMP2P_SET_REMOTE_PID(hdr, pid) \
|
||||
SMP2P_SET_BITS(hdr, SMP2P_REMOTE_PID_MASK, SMP2P_REMOTE_PID_BIT, pid)
|
||||
|
||||
#define SMP2P_GET_VERSION(hdr) \
|
||||
SMP2P_GET_BITS(hdr, SMP2P_VERSION_MASK, SMP2P_VERSION_BIT)
|
||||
#define SMP2P_SET_VERSION(hdr, version) \
|
||||
SMP2P_SET_BITS(hdr, SMP2P_VERSION_MASK, SMP2P_VERSION_BIT, version)
|
||||
|
||||
#define SMP2P_GET_FEATURES(hdr) \
|
||||
SMP2P_GET_BITS(hdr, SMP2P_FEATURE_MASK, SMP2P_FEATURE_BIT)
|
||||
#define SMP2P_SET_FEATURES(hdr, features) \
|
||||
SMP2P_SET_BITS(hdr, SMP2P_FEATURE_MASK, SMP2P_FEATURE_BIT, features)
|
||||
|
||||
#define SMP2P_GET_ENT_TOTAL(hdr) \
|
||||
SMP2P_GET_BITS(hdr, SMP2P_ENT_TOTAL_MASK, SMP2P_ENT_TOTAL_BIT)
|
||||
#define SMP2P_SET_ENT_TOTAL(hdr, entries) \
|
||||
SMP2P_SET_BITS(hdr, SMP2P_ENT_TOTAL_MASK, SMP2P_ENT_TOTAL_BIT, entries)
|
||||
|
||||
#define SMP2P_GET_ENT_VALID(hdr) \
|
||||
SMP2P_GET_BITS(hdr, SMP2P_ENT_VALID_MASK, SMP2P_ENT_VALID_BIT)
|
||||
#define SMP2P_SET_ENT_VALID(hdr, entries) \
|
||||
SMP2P_SET_BITS(hdr, SMP2P_ENT_VALID_MASK, SMP2P_ENT_VALID_BIT,\
|
||||
entries)
|
||||
|
||||
/* Loopback Command Macros */
|
||||
#define SMP2P_RMT_CMD_TYPE_MASK 0x80000000
|
||||
#define SMP2P_RMT_CMD_TYPE_BIT 31
|
||||
#define SMP2P_RMT_IGNORE_MASK 0x40000000
|
||||
#define SMP2P_RMT_IGNORE_BIT 30
|
||||
#define SMP2P_RMT_CMD_MASK 0x3f000000
|
||||
#define SMP2P_RMT_CMD_BIT 24
|
||||
#define SMP2P_RMT_DATA_MASK 0x00ffffff
|
||||
#define SMP2P_RMT_DATA_BIT 0
|
||||
|
||||
#define SMP2P_GET_RMT_CMD_TYPE(val) \
|
||||
SMP2P_GET_BITS(val, SMP2P_RMT_CMD_TYPE_MASK, SMP2P_RMT_CMD_TYPE_BIT)
|
||||
#define SMP2P_GET_RMT_CMD(val) \
|
||||
SMP2P_GET_BITS(val, SMP2P_RMT_CMD_MASK, SMP2P_RMT_CMD_BIT)
|
||||
|
||||
#define SMP2P_GET_RMT_DATA(val) \
|
||||
SMP2P_GET_BITS(val, SMP2P_RMT_DATA_MASK, SMP2P_RMT_DATA_BIT)
|
||||
|
||||
#define SMP2P_SET_RMT_CMD_TYPE(val, cmd_type) \
|
||||
SMP2P_SET_BITS(val, SMP2P_RMT_CMD_TYPE_MASK, SMP2P_RMT_CMD_TYPE_BIT, \
|
||||
cmd_type)
|
||||
#define SMP2P_SET_RMT_CMD_TYPE_REQ(val) \
|
||||
SMP2P_SET_RMT_CMD_TYPE(val, 1)
|
||||
#define SMP2P_SET_RMT_CMD_TYPE_RESP(val) \
|
||||
SMP2P_SET_RMT_CMD_TYPE(val, 0)
|
||||
|
||||
#define SMP2P_SET_RMT_CMD(val, cmd) \
|
||||
SMP2P_SET_BITS(val, SMP2P_RMT_CMD_MASK, SMP2P_RMT_CMD_BIT, \
|
||||
cmd)
|
||||
#define SMP2P_SET_RMT_DATA(val, data) \
|
||||
SMP2P_SET_BITS(val, SMP2P_RMT_DATA_MASK, SMP2P_RMT_DATA_BIT, data)
|
||||
|
||||
enum {
|
||||
SMP2P_LB_CMD_NOOP = 0x0,
|
||||
SMP2P_LB_CMD_ECHO,
|
||||
SMP2P_LB_CMD_CLEARALL,
|
||||
SMP2P_LB_CMD_PINGPONG,
|
||||
SMP2P_LB_CMD_RSPIN_START,
|
||||
SMP2P_LB_CMD_RSPIN_LOCKED,
|
||||
SMP2P_LB_CMD_RSPIN_UNLOCKED,
|
||||
SMP2P_LB_CMD_RSPIN_END,
|
||||
};
|
||||
#define SMP2P_RLPB_IGNORE 0x40
|
||||
#define SMP2P_RLPB_ENTRY_NAME "smp2p"
|
||||
|
||||
/* Debug Logging Macros */
|
||||
enum {
|
||||
MSM_SMP2P_INFO = 1U << 0,
|
||||
MSM_SMP2P_DEBUG = 1U << 1,
|
||||
MSM_SMP2P_GPIO = 1U << 2,
|
||||
};
|
||||
|
||||
#define SMP2P_IPC_LOG_STR(x...) do { \
|
||||
if (smp2p_get_log_ctx()) \
|
||||
ipc_log_string(smp2p_get_log_ctx(), x); \
|
||||
} while (0)
|
||||
|
||||
#define SMP2P_DBG(x...) do { \
|
||||
if (smp2p_get_debug_mask() & MSM_SMP2P_DEBUG) \
|
||||
SMP2P_IPC_LOG_STR(x); \
|
||||
} while (0)
|
||||
|
||||
#define SMP2P_INFO(x...) do { \
|
||||
if (smp2p_get_debug_mask() & MSM_SMP2P_INFO) \
|
||||
SMP2P_IPC_LOG_STR(x); \
|
||||
} while (0)
|
||||
|
||||
#define SMP2P_ERR(x...) do { \
|
||||
pr_err(x); \
|
||||
SMP2P_IPC_LOG_STR(x); \
|
||||
} while (0)
|
||||
|
||||
#define SMP2P_GPIO(x...) do { \
|
||||
if (smp2p_get_debug_mask() & MSM_SMP2P_GPIO) \
|
||||
SMP2P_IPC_LOG_STR(x); \
|
||||
} while (0)
|
||||
|
||||
|
||||
enum msm_smp2p_edge_state {
|
||||
SMP2P_EDGE_STATE_CLOSED,
|
||||
SMP2P_EDGE_STATE_OPENING,
|
||||
SMP2P_EDGE_STATE_OPENED,
|
||||
SMP2P_EDGE_STATE_FAILED = 0xff,
|
||||
};
|
||||
|
||||
struct smp2p_smem {
|
||||
uint32_t magic;
|
||||
uint32_t feature_version;
|
||||
uint32_t rem_loc_proc_id;
|
||||
uint32_t valid_total_ent;
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
struct smp2p_entry_v1 {
|
||||
char name[SMP2P_MAX_ENTRY_NAME];
|
||||
uint32_t entry;
|
||||
};
|
||||
|
||||
struct smp2p_smem_item {
|
||||
struct smp2p_smem header;
|
||||
struct smp2p_entry_v1 entries[SMP2P_MAX_ENTRY];
|
||||
};
|
||||
|
||||
/* Mock object for internal loopback testing. */
|
||||
struct msm_smp2p_remote_mock {
|
||||
struct smp2p_smem_item remote_item;
|
||||
int rx_interrupt_count;
|
||||
int (*rx_interrupt)(void);
|
||||
void (*tx_interrupt)(void);
|
||||
|
||||
bool item_exists;
|
||||
bool initialized;
|
||||
struct completion cb_completion;
|
||||
};
|
||||
|
||||
void smp2p_init_header(struct smp2p_smem *header_ptr, int local_pid,
|
||||
int remote_pid, uint32_t features, uint32_t version);
|
||||
void *msm_smp2p_get_remote_mock(void);
|
||||
int smp2p_remote_mock_rx_interrupt(void);
|
||||
int smp2p_reset_mock_edge(void);
|
||||
void msm_smp2p_interrupt_handler(int);
|
||||
void msm_smp2p_set_remote_mock_exists(bool item_exists);
|
||||
void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size);
|
||||
void *msm_smp2p_init_rmt_lpb_proc(int remote_pid);
|
||||
int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid);
|
||||
void *smp2p_get_log_ctx(void);
|
||||
int smp2p_get_debug_mask(void);
|
||||
|
||||
/* Inbound / outbound Interrupt configuration. */
|
||||
struct smp2p_interrupt_config {
|
||||
bool is_configured;
|
||||
uint32_t *out_int_ptr;
|
||||
uint32_t out_int_mask;
|
||||
int in_int_id;
|
||||
const char *name;
|
||||
|
||||
/* interrupt stats */
|
||||
unsigned in_interrupt_count;
|
||||
unsigned out_interrupt_count;
|
||||
};
|
||||
|
||||
struct smp2p_interrupt_config *smp2p_get_interrupt_config(void);
|
||||
const char *smp2p_pid_to_name(int remote_pid);
|
||||
struct smp2p_smem *smp2p_get_in_item(int remote_pid);
|
||||
struct smp2p_smem *smp2p_get_out_item(int remote_pid, int *state);
|
||||
void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open);
|
||||
#endif
|
|
@ -0,0 +1,79 @@
|
|||
/* arch/arm/mach-msm/smp2p_private_api.h
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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.
|
||||
*/
|
||||
#ifndef _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_
|
||||
#define _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_
|
||||
|
||||
#include <linux/notifier.h>
|
||||
|
||||
struct msm_smp2p_out;
|
||||
|
||||
/* Maximum size of the entry name and trailing null. */
|
||||
#define SMP2P_MAX_ENTRY_NAME 16
|
||||
|
||||
/* Bits per entry */
|
||||
#define SMP2P_BITS_PER_ENTRY 32
|
||||
|
||||
/* Processor ID's */
|
||||
enum {
|
||||
SMP2P_APPS_PROC = 0,
|
||||
SMP2P_MODEM_PROC = 1,
|
||||
SMP2P_AUDIO_PROC = 2,
|
||||
SMP2P_RESERVED_PROC_1 = 3,
|
||||
SMP2P_WIRELESS_PROC = 4,
|
||||
SMP2P_RESERVED_PROC_2 = 5,
|
||||
SMP2P_POWER_PROC = 6,
|
||||
/* add new processors here */
|
||||
|
||||
SMP2P_REMOTE_MOCK_PROC,
|
||||
SMP2P_NUM_PROCS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Notification events that are passed to notifier for incoming and outgoing
|
||||
* entries.
|
||||
*
|
||||
* If the @metadata argument in the notifier is non-null, then it will
|
||||
* point to the associated struct smux_meta_* structure.
|
||||
*/
|
||||
enum msm_smp2p_events {
|
||||
SMP2P_OPEN, /* data is NULL */
|
||||
SMP2P_ENTRY_UPDATE, /* data => struct msm_smp2p_update_notif */
|
||||
};
|
||||
|
||||
/**
|
||||
* Passed in response to a SMP2P_ENTRY_UPDATE event.
|
||||
*
|
||||
* @prev_value: previous value of entry
|
||||
* @current_value: latest value of entry
|
||||
*/
|
||||
struct msm_smp2p_update_notif {
|
||||
uint32_t previous_value;
|
||||
uint32_t current_value;
|
||||
};
|
||||
|
||||
int msm_smp2p_out_open(int remote_pid, const char *entry,
|
||||
struct notifier_block *open_notifier,
|
||||
struct msm_smp2p_out **handle);
|
||||
int msm_smp2p_out_close(struct msm_smp2p_out **handle);
|
||||
int msm_smp2p_out_read(struct msm_smp2p_out *handle, uint32_t *data);
|
||||
int msm_smp2p_out_write(struct msm_smp2p_out *handle, uint32_t data);
|
||||
int msm_smp2p_out_modify(struct msm_smp2p_out *handle, uint32_t set_mask,
|
||||
uint32_t clear_mask);
|
||||
int msm_smp2p_in_read(int remote_pid, const char *entry, uint32_t *data);
|
||||
int msm_smp2p_in_register(int remote_pid, const char *entry,
|
||||
struct notifier_block *in_notifier);
|
||||
int msm_smp2p_in_unregister(int remote_pid, const char *entry,
|
||||
struct notifier_block *in_notifier);
|
||||
|
||||
#endif /* _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_ */
|
|
@ -0,0 +1,487 @@
|
|||
/* arch/arm/mach-msm/smp2p_spinlock_test.c
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/debugfs.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/remote_spinlock.h>
|
||||
#include <mach/msm_smsm.h>
|
||||
#include "smd_private.h"
|
||||
#include "smp2p_private.h"
|
||||
#include "smp2p_test_common.h"
|
||||
|
||||
#define REMOTE_SPIN_PID 1
|
||||
#define RS_END_THIEF_PID_BIT 20
|
||||
#define RS_END_THIEF_MASK 0x00f00000
|
||||
|
||||
/* Spinlock commands used for testing Apps<->RPM spinlocks. */
|
||||
enum RPM_SPINLOCK_CMDS {
|
||||
RPM_CMD_INVALID,
|
||||
RPM_CMD_START,
|
||||
RPM_CMD_LOCKED,
|
||||
RPM_CMD_UNLOCKED,
|
||||
RPM_CMD_END,
|
||||
};
|
||||
|
||||
/* Shared structure for testing Apps<->RPM spinlocks. */
|
||||
struct rpm_spinlock_test {
|
||||
uint32_t apps_cmd;
|
||||
uint32_t apps_lock_count;
|
||||
uint32_t rpm_cmd;
|
||||
uint32_t rpm_lock_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_spinlock_core - Verify remote spinlock.
|
||||
*
|
||||
* @s: Pointer to output file
|
||||
* @remote_pid: Remote processor to test
|
||||
* @use_trylock: Use trylock to prevent an Apps deadlock if the
|
||||
* remote spinlock fails.
|
||||
*/
|
||||
static void smp2p_ut_remote_spinlock_core(struct seq_file *s, int remote_pid,
|
||||
bool use_trylock)
|
||||
{
|
||||
int failed = 0;
|
||||
unsigned lock_count = 0;
|
||||
struct msm_smp2p_out *handle = NULL;
|
||||
int ret;
|
||||
uint32_t test_request;
|
||||
uint32_t test_response;
|
||||
struct mock_cb_data cb_out;
|
||||
struct mock_cb_data cb_in;
|
||||
unsigned long flags;
|
||||
unsigned n;
|
||||
unsigned test_num;
|
||||
bool have_lock;
|
||||
bool timeout;
|
||||
int failed_tmp;
|
||||
int spinlock_owner;
|
||||
remote_spinlock_t *smem_spinlock;
|
||||
|
||||
seq_printf(s, "Running %s for '%s' remote pid %d\n",
|
||||
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
|
||||
|
||||
cb_out.initialized = false;
|
||||
cb_in.initialized = false;
|
||||
mock_cb_data_init(&cb_out);
|
||||
mock_cb_data_init(&cb_in);
|
||||
do {
|
||||
smem_spinlock = smem_get_remote_spinlock();
|
||||
UT_ASSERT_PTR(smem_spinlock, !=, NULL);
|
||||
|
||||
/* Open output entry */
|
||||
ret = msm_smp2p_out_open(remote_pid, SMP2P_RLPB_ENTRY_NAME,
|
||||
&cb_out.nb, &handle);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_out.cb_completion, HZ * 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_out.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_out.event_open, ==, 1);
|
||||
|
||||
/* Open inbound entry */
|
||||
ret = msm_smp2p_in_register(remote_pid, SMP2P_RLPB_ENTRY_NAME,
|
||||
&cb_in.nb);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ * 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in.event_open, ==, 1);
|
||||
|
||||
/* Send start */
|
||||
mock_cb_data_reset(&cb_in);
|
||||
mock_cb_data_reset(&cb_out);
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_START);
|
||||
SMP2P_SET_RMT_DATA(test_request, 0x0);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ * 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
|
||||
ret = msm_smp2p_in_read(remote_pid, SMP2P_RLPB_ENTRY_NAME,
|
||||
&test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
test_response = SMP2P_GET_RMT_CMD(test_response);
|
||||
if (test_response != SMP2P_LB_CMD_RSPIN_LOCKED &&
|
||||
test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED) {
|
||||
/* invalid response from remote - abort test */
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
|
||||
SMP2P_SET_RMT_DATA(test_request, 0x0);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_LOCKED, ==,
|
||||
test_response);
|
||||
}
|
||||
|
||||
/* Run spinlock test */
|
||||
if (use_trylock)
|
||||
seq_printf(s, "\tUsing remote_spin_trylock\n");
|
||||
else
|
||||
seq_printf(s, "\tUsing remote_spin_lock\n");
|
||||
|
||||
flags = 0;
|
||||
have_lock = false;
|
||||
timeout = false;
|
||||
spinlock_owner = 0;
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
|
||||
for (test_num = 0; !failed && test_num < 10000; ++test_num) {
|
||||
/* try to acquire spinlock */
|
||||
if (use_trylock) {
|
||||
unsigned long j_start = jiffies;
|
||||
while (!remote_spin_trylock_irqsave(
|
||||
smem_spinlock, flags)) {
|
||||
if (jiffies_to_msecs(jiffies - j_start)
|
||||
> 1000) {
|
||||
seq_printf(s,
|
||||
"\tFail: Timeout trying to get the lock\n");
|
||||
timeout = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (timeout)
|
||||
break;
|
||||
} else {
|
||||
remote_spin_lock_irqsave(smem_spinlock, flags);
|
||||
}
|
||||
have_lock = true;
|
||||
++lock_count;
|
||||
|
||||
/* tell the remote side that we have the lock */
|
||||
SMP2P_SET_RMT_DATA(test_request, lock_count);
|
||||
SMP2P_SET_RMT_CMD(test_request,
|
||||
SMP2P_LB_CMD_RSPIN_LOCKED);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
/* verify the other side doesn't say it has the lock */
|
||||
for (n = 0; n < 1000; ++n) {
|
||||
spinlock_owner =
|
||||
remote_spin_owner(smem_spinlock);
|
||||
if (spinlock_owner != REMOTE_SPIN_PID) {
|
||||
/* lock stolen by remote side */
|
||||
seq_printf(s,
|
||||
"\tFail: Remote side (%d) stole lock (pid %d)\n",
|
||||
remote_pid, spinlock_owner);
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
spinlock_owner = 0;
|
||||
|
||||
ret = msm_smp2p_in_read(remote_pid,
|
||||
SMP2P_RLPB_ENTRY_NAME, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
test_response =
|
||||
SMP2P_GET_RMT_CMD(test_response);
|
||||
UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_UNLOCKED, ==,
|
||||
test_response);
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* tell remote side we are unlocked and release lock */
|
||||
SMP2P_SET_RMT_CMD(test_request,
|
||||
SMP2P_LB_CMD_RSPIN_UNLOCKED);
|
||||
(void)msm_smp2p_out_write(handle, test_request);
|
||||
have_lock = false;
|
||||
remote_spin_unlock_irqrestore(smem_spinlock, flags);
|
||||
}
|
||||
if (have_lock)
|
||||
remote_spin_unlock_irqrestore(smem_spinlock, flags);
|
||||
|
||||
/* End test */
|
||||
mock_cb_data_reset(&cb_in);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
|
||||
SMP2P_SET_RMT_DATA(test_request, lock_count |
|
||||
(spinlock_owner << RS_END_THIEF_PID_BIT));
|
||||
(void)msm_smp2p_out_write(handle, test_request);
|
||||
|
||||
failed_tmp = failed;
|
||||
failed = false;
|
||||
do {
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ * 2),
|
||||
>, 0);
|
||||
INIT_COMPLETION(cb_in.cb_completion);
|
||||
ret = msm_smp2p_in_read(remote_pid,
|
||||
SMP2P_RLPB_ENTRY_NAME, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
} while (!failed &&
|
||||
SMP2P_GET_RMT_CMD(test_response) !=
|
||||
SMP2P_LB_CMD_RSPIN_END);
|
||||
if (failed)
|
||||
break;
|
||||
failed = failed_tmp;
|
||||
|
||||
test_response = SMP2P_GET_RMT_DATA(test_response);
|
||||
seq_printf(s,
|
||||
"\tLocked spinlock local %u times; remote %u times",
|
||||
lock_count,
|
||||
test_response & ((1 << RS_END_THIEF_PID_BIT) - 1)
|
||||
);
|
||||
if (test_response & RS_END_THIEF_MASK) {
|
||||
seq_printf(s,
|
||||
"Remote side reporting lock stolen by pid %d.\n",
|
||||
SMP2P_GET_BITS(test_response,
|
||||
RS_END_THIEF_MASK,
|
||||
RS_END_THIEF_PID_BIT));
|
||||
failed = 1;
|
||||
}
|
||||
seq_printf(s, "\n");
|
||||
|
||||
/* Cleanup */
|
||||
ret = msm_smp2p_out_close(&handle);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_PTR(handle, ==, NULL);
|
||||
ret = msm_smp2p_in_unregister(remote_pid,
|
||||
SMP2P_RLPB_ENTRY_NAME, &cb_in.nb);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
if (!failed && !timeout)
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
if (handle) {
|
||||
/* send end command */
|
||||
test_request = 0;
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
|
||||
SMP2P_SET_RMT_DATA(test_request, lock_count);
|
||||
(void)msm_smp2p_out_write(handle, test_request);
|
||||
(void)msm_smp2p_out_close(&handle);
|
||||
}
|
||||
(void)msm_smp2p_in_unregister(remote_pid,
|
||||
SMP2P_RLPB_ENTRY_NAME, &cb_in.nb);
|
||||
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_spinlock_pid - Verify remote spinlock for a processor.
|
||||
*
|
||||
* @s: Pointer to output file
|
||||
* @pid: Processor to test
|
||||
* @use_trylock: Use trylock to prevent an Apps deadlock if the
|
||||
* remote spinlock fails.
|
||||
*/
|
||||
static void smp2p_ut_remote_spinlock_pid(struct seq_file *s, int pid,
|
||||
bool use_trylock)
|
||||
{
|
||||
struct smp2p_interrupt_config *int_cfg;
|
||||
|
||||
int_cfg = smp2p_get_interrupt_config();
|
||||
if (!int_cfg) {
|
||||
seq_printf(s, "Remote processor config unavailable\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pid >= SMP2P_NUM_PROCS || !int_cfg[pid].is_configured)
|
||||
return;
|
||||
|
||||
msm_smp2p_deinit_rmt_lpb_proc(pid);
|
||||
smp2p_ut_remote_spinlock_core(s, pid, use_trylock);
|
||||
msm_smp2p_init_rmt_lpb_proc(pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_spinlock - Verify remote spinlock for all processors.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*/
|
||||
static void smp2p_ut_remote_spinlock(struct seq_file *s)
|
||||
{
|
||||
int pid;
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
|
||||
smp2p_ut_remote_spinlock_pid(s, pid, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_spin_trylock - Verify remote trylock for all processors.
|
||||
*
|
||||
* @s: Pointer to output file
|
||||
*/
|
||||
static void smp2p_ut_remote_spin_trylock(struct seq_file *s)
|
||||
{
|
||||
int pid;
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
|
||||
smp2p_ut_remote_spinlock_pid(s, pid, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_spinlock - Verify remote spinlock for all processors.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test verifies inbound and outbound functionality for all
|
||||
* configured remote processor.
|
||||
*/
|
||||
static void smp2p_ut_remote_spinlock_modem(struct seq_file *s)
|
||||
{
|
||||
smp2p_ut_remote_spinlock_pid(s, SMP2P_MODEM_PROC, false);
|
||||
}
|
||||
|
||||
static void smp2p_ut_remote_spinlock_adsp(struct seq_file *s)
|
||||
{
|
||||
smp2p_ut_remote_spinlock_pid(s, SMP2P_AUDIO_PROC, false);
|
||||
}
|
||||
|
||||
static void smp2p_ut_remote_spinlock_wcnss(struct seq_file *s)
|
||||
{
|
||||
smp2p_ut_remote_spinlock_pid(s, SMP2P_WIRELESS_PROC, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_spinlock_rpm - Verify remote spinlock.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
* @remote_pid: Remote processor to test
|
||||
*/
|
||||
static void smp2p_ut_remote_spinlock_rpm(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
unsigned long flags;
|
||||
unsigned n;
|
||||
unsigned test_num;
|
||||
struct rpm_spinlock_test *data_ptr;
|
||||
remote_spinlock_t *smem_spinlock;
|
||||
bool have_lock;
|
||||
|
||||
seq_printf(s, "Running %s for Apps<->RPM Test\n",
|
||||
__func__);
|
||||
do {
|
||||
smem_spinlock = smem_get_remote_spinlock();
|
||||
UT_ASSERT_PTR(smem_spinlock, !=, NULL);
|
||||
|
||||
data_ptr = smem_alloc2(SMEM_ID_VENDOR0,
|
||||
sizeof(struct rpm_spinlock_test));
|
||||
UT_ASSERT_PTR(0, !=, data_ptr);
|
||||
|
||||
/* Send start */
|
||||
writel_relaxed(0, &data_ptr->apps_lock_count);
|
||||
writel_relaxed(RPM_CMD_START, &data_ptr->apps_cmd);
|
||||
|
||||
seq_printf(s, "\tWaiting for RPM to start test\n");
|
||||
for (n = 0; n < 1000; ++n) {
|
||||
if (readl_relaxed(&data_ptr->rpm_cmd) !=
|
||||
RPM_CMD_INVALID)
|
||||
break;
|
||||
usleep(1000);
|
||||
}
|
||||
if (readl_relaxed(&data_ptr->rpm_cmd) == RPM_CMD_INVALID) {
|
||||
/* timeout waiting for RPM */
|
||||
writel_relaxed(RPM_CMD_INVALID, &data_ptr->apps_cmd);
|
||||
UT_ASSERT_INT(RPM_CMD_LOCKED, !=, RPM_CMD_INVALID);
|
||||
}
|
||||
|
||||
/* Run spinlock test */
|
||||
flags = 0;
|
||||
have_lock = false;
|
||||
for (test_num = 0; !failed && test_num < 10000; ++test_num) {
|
||||
/* acquire spinlock */
|
||||
remote_spin_lock_irqsave(smem_spinlock, flags);
|
||||
have_lock = true;
|
||||
writel_relaxed(++data_ptr->apps_lock_count,
|
||||
&data_ptr->apps_lock_count);
|
||||
writel_relaxed(RPM_CMD_LOCKED, &data_ptr->apps_cmd);
|
||||
/*
|
||||
* Ensure that the remote side sees our lock has
|
||||
* been acquired before we start polling their status.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* verify the other side doesn't say it has the lock */
|
||||
for (n = 0; n < 1000; ++n) {
|
||||
UT_ASSERT_HEX(RPM_CMD_UNLOCKED, ==,
|
||||
readl_relaxed(&data_ptr->rpm_cmd));
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* release spinlock */
|
||||
have_lock = false;
|
||||
writel_relaxed(RPM_CMD_UNLOCKED, &data_ptr->apps_cmd);
|
||||
/*
|
||||
* Ensure that our status-update write was committed
|
||||
* before we unlock the spinlock.
|
||||
*/
|
||||
wmb();
|
||||
remote_spin_unlock_irqrestore(smem_spinlock, flags);
|
||||
}
|
||||
if (have_lock)
|
||||
remote_spin_unlock_irqrestore(smem_spinlock, flags);
|
||||
|
||||
/* End test */
|
||||
writel_relaxed(RPM_CMD_INVALID, &data_ptr->apps_cmd);
|
||||
seq_printf(s,
|
||||
"\tLocked spinlock local %u remote %u\n",
|
||||
readl_relaxed(&data_ptr->apps_lock_count),
|
||||
readl_relaxed(&data_ptr->rpm_lock_count));
|
||||
|
||||
if (!failed)
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int __init smp2p_debugfs_init(void)
|
||||
{
|
||||
/*
|
||||
* Add Unit Test entries.
|
||||
*
|
||||
* The idea with unit tests is that you can run all of them
|
||||
* from ADB shell by doing:
|
||||
* adb shell
|
||||
* cat ut*
|
||||
*
|
||||
* And if particular tests fail, you can then repeatedly run the
|
||||
* failing tests as you debug and resolve the failing test.
|
||||
*/
|
||||
smp2p_debug_create("ut_remote_spinlock",
|
||||
smp2p_ut_remote_spinlock);
|
||||
smp2p_debug_create("ut_remote_spin_trylock",
|
||||
smp2p_ut_remote_spin_trylock);
|
||||
smp2p_debug_create("ut_remote_spinlock_modem",
|
||||
smp2p_ut_remote_spinlock_modem);
|
||||
smp2p_debug_create("ut_remote_spinlock_adsp",
|
||||
smp2p_ut_remote_spinlock_adsp);
|
||||
smp2p_debug_create("ut_remote_spinlock_wcnss",
|
||||
smp2p_ut_remote_spinlock_wcnss);
|
||||
smp2p_debug_create("ut_remote_spinlock_rpm",
|
||||
smp2p_ut_remote_spinlock_rpm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(smp2p_debugfs_init);
|
|
@ -0,0 +1,913 @@
|
|||
/* arch/arm/mach-msm/smp2p_test.c
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/debugfs.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/completion.h>
|
||||
#include "smp2p_private.h"
|
||||
#include "smp2p_test_common.h"
|
||||
|
||||
/**
|
||||
* smp2p_ut_local_basic - Basic sanity test using local loopback.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test simulates a simple write and read
|
||||
* when remote processor does not exist.
|
||||
*/
|
||||
static void smp2p_ut_local_basic(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
struct msm_smp2p_out *smp2p_obj;
|
||||
struct msm_smp2p_remote_mock *rmp = NULL;
|
||||
int ret;
|
||||
uint32_t test_request;
|
||||
uint32_t test_response = 0;
|
||||
static struct mock_cb_data cb_data;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
mock_cb_data_init(&cb_data);
|
||||
do {
|
||||
/* initialize mock edge and start opening */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
rmp = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(rmp, !=, NULL);
|
||||
|
||||
rmp->rx_interrupt_count = 0;
|
||||
memset(&rmp->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
|
||||
msm_smp2p_set_remote_mock_exists(false);
|
||||
|
||||
ret = msm_smp2p_out_open(SMP2P_REMOTE_MOCK_PROC, "smp2p",
|
||||
&cb_data.nb, &smp2p_obj);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_data.cb_count, ==, 0);
|
||||
rmp->rx_interrupt_count = 0;
|
||||
|
||||
/* simulate response from remote side */
|
||||
rmp->remote_item.header.magic = SMP2P_MAGIC;
|
||||
SMP2P_SET_LOCAL_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_REMOTE_MOCK_PROC);
|
||||
SMP2P_SET_REMOTE_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_APPS_PROC);
|
||||
SMP2P_SET_VERSION(
|
||||
rmp->remote_item.header.feature_version, 1);
|
||||
SMP2P_SET_FEATURES(
|
||||
rmp->remote_item.header.feature_version, 0);
|
||||
SMP2P_SET_ENT_TOTAL(
|
||||
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
rmp->remote_item.header.valid_total_ent, 0);
|
||||
rmp->remote_item.header.reserved = 0x0;
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
rmp->tx_interrupt();
|
||||
|
||||
/* verify port was opened */
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_data.cb_completion, HZ / 2), >, 0);
|
||||
UT_ASSERT_INT(cb_data.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_data.event_open, ==, 1);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
|
||||
|
||||
/* do write (test outbound entries) */
|
||||
rmp->rx_interrupt_count = 0;
|
||||
test_request = 0xC0DE;
|
||||
ret = msm_smp2p_out_write(smp2p_obj, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
|
||||
/* do read (test inbound entries) */
|
||||
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(test_request, ==, test_response);
|
||||
|
||||
ret = msm_smp2p_out_close(&smp2p_obj);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_PTR(smp2p_obj, ==, 0);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
(void)msm_smp2p_out_close(&smp2p_obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_local_late_open - Verify post-negotiation opening.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* Verify entry creation for opening entries after negotiation is complete.
|
||||
*/
|
||||
static void smp2p_ut_local_late_open(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
struct msm_smp2p_out *smp2p_obj;
|
||||
struct msm_smp2p_remote_mock *rmp = NULL;
|
||||
int ret;
|
||||
uint32_t test_request;
|
||||
uint32_t test_response = 0;
|
||||
static struct mock_cb_data cb_data;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
mock_cb_data_init(&cb_data);
|
||||
do {
|
||||
/* initialize mock edge */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
rmp = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(rmp, !=, NULL);
|
||||
|
||||
rmp->rx_interrupt_count = 0;
|
||||
memset(&rmp->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
rmp->remote_item.header.magic = SMP2P_MAGIC;
|
||||
SMP2P_SET_LOCAL_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_REMOTE_MOCK_PROC);
|
||||
SMP2P_SET_REMOTE_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_APPS_PROC);
|
||||
SMP2P_SET_VERSION(
|
||||
rmp->remote_item.header.feature_version, 1);
|
||||
SMP2P_SET_FEATURES(
|
||||
rmp->remote_item.header.feature_version, 0);
|
||||
SMP2P_SET_ENT_TOTAL(
|
||||
rmp->remote_item.header.valid_total_ent,
|
||||
SMP2P_MAX_ENTRY);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
rmp->remote_item.header.valid_total_ent, 0);
|
||||
rmp->remote_item.header.reserved = 0x0;
|
||||
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
|
||||
ret = msm_smp2p_out_open(SMP2P_REMOTE_MOCK_PROC, "smp2p",
|
||||
&cb_data.nb, &smp2p_obj);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
/* verify port was opened */
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_data.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_data.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_data.event_open, ==, 1);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
|
||||
|
||||
/* do write (test outbound entries) */
|
||||
rmp->rx_interrupt_count = 0;
|
||||
test_request = 0xC0DE;
|
||||
ret = msm_smp2p_out_write(smp2p_obj, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
|
||||
/* do read (test inbound entries) */
|
||||
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(test_request, ==, test_response);
|
||||
|
||||
ret = msm_smp2p_out_close(&smp2p_obj);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_PTR(smp2p_obj, ==, 0);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
(void)msm_smp2p_out_close(&smp2p_obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_local_early_open - Verify pre-negotiation opening.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* Verify entry creation for opening entries before negotiation is complete.
|
||||
*/
|
||||
static void smp2p_ut_local_early_open(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
struct msm_smp2p_out *smp2p_obj;
|
||||
struct msm_smp2p_remote_mock *rmp = NULL;
|
||||
struct smp2p_smem *outbound_item;
|
||||
int negotiation_state;
|
||||
int ret;
|
||||
uint32_t test_request;
|
||||
uint32_t test_response = 0;
|
||||
static struct mock_cb_data cb_data;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
mock_cb_data_init(&cb_data);
|
||||
do {
|
||||
/* initialize mock edge, but don't enable, yet */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
rmp = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(rmp, !=, NULL);
|
||||
|
||||
rmp->rx_interrupt_count = 0;
|
||||
memset(&rmp->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
rmp->remote_item.header.magic = SMP2P_MAGIC;
|
||||
SMP2P_SET_LOCAL_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_REMOTE_MOCK_PROC);
|
||||
SMP2P_SET_REMOTE_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_APPS_PROC);
|
||||
SMP2P_SET_VERSION(
|
||||
rmp->remote_item.header.feature_version, 1);
|
||||
SMP2P_SET_FEATURES(
|
||||
rmp->remote_item.header.feature_version, 0);
|
||||
SMP2P_SET_ENT_TOTAL(
|
||||
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
rmp->remote_item.header.valid_total_ent, 0);
|
||||
rmp->remote_item.header.reserved = 0x0;
|
||||
|
||||
msm_smp2p_set_remote_mock_exists(false);
|
||||
UT_ASSERT_PTR(NULL, ==,
|
||||
smp2p_get_in_item(SMP2P_REMOTE_MOCK_PROC));
|
||||
|
||||
/* initiate open, but verify it doesn't complete */
|
||||
ret = msm_smp2p_out_open(SMP2P_REMOTE_MOCK_PROC, "smp2p",
|
||||
&cb_data.nb, &smp2p_obj);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_data.cb_completion, HZ / 8),
|
||||
==, 0);
|
||||
UT_ASSERT_INT(cb_data.cb_count, ==, 0);
|
||||
UT_ASSERT_INT(cb_data.event_open, ==, 0);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
|
||||
outbound_item = smp2p_get_out_item(SMP2P_REMOTE_MOCK_PROC,
|
||||
&negotiation_state);
|
||||
UT_ASSERT_PTR(outbound_item, !=, NULL);
|
||||
UT_ASSERT_INT(negotiation_state, ==, SMP2P_EDGE_STATE_OPENING);
|
||||
UT_ASSERT_INT(0, ==,
|
||||
SMP2P_GET_ENT_VALID(outbound_item->valid_total_ent));
|
||||
|
||||
/* verify that read/write don't work yet */
|
||||
rmp->rx_interrupt_count = 0;
|
||||
test_request = 0x0;
|
||||
ret = msm_smp2p_out_write(smp2p_obj, test_request);
|
||||
UT_ASSERT_INT(ret, ==, -ENODEV);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 0);
|
||||
|
||||
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, -ENODEV);
|
||||
|
||||
/* allocate remote entry and verify open */
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
rmp->tx_interrupt();
|
||||
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_data.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_data.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_data.event_open, ==, 1);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
|
||||
|
||||
/* do write (test outbound entries) */
|
||||
rmp->rx_interrupt_count = 0;
|
||||
test_request = 0xC0DE;
|
||||
ret = msm_smp2p_out_write(smp2p_obj, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
|
||||
/* do read (test inbound entries) */
|
||||
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(test_request, ==, test_response);
|
||||
|
||||
ret = msm_smp2p_out_close(&smp2p_obj);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_PTR(smp2p_obj, ==, 0);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
(void)msm_smp2p_out_close(&smp2p_obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_mock_loopback - Exercise the remote loopback using remote mock.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test exercises the remote loopback code using
|
||||
* remote mock object. The remote mock object simulates the remote
|
||||
* processor sending remote loopback commands to the local processor.
|
||||
*/
|
||||
static void smp2p_ut_mock_loopback(struct seq_file *s)
|
||||
{
|
||||
int failed = 0;
|
||||
struct msm_smp2p_remote_mock *rmp = NULL;
|
||||
int ret;
|
||||
uint32_t test_request = 0;
|
||||
uint32_t test_response = 0;
|
||||
struct msm_smp2p_out *local;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
do {
|
||||
/* Initialize the mock edge */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
rmp = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(rmp, !=, NULL);
|
||||
|
||||
memset(&rmp->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
rmp->remote_item.header.magic = SMP2P_MAGIC;
|
||||
SMP2P_SET_LOCAL_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_REMOTE_MOCK_PROC);
|
||||
SMP2P_SET_REMOTE_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_APPS_PROC);
|
||||
SMP2P_SET_VERSION(
|
||||
rmp->remote_item.header.feature_version, 1);
|
||||
SMP2P_SET_FEATURES(
|
||||
rmp->remote_item.header.feature_version, 0);
|
||||
SMP2P_SET_ENT_TOTAL(
|
||||
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
rmp->remote_item.header.valid_total_ent, 1);
|
||||
rmp->remote_item.header.reserved = 0x0;
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
|
||||
/* Create test entry and attach loopback server */
|
||||
rmp->rx_interrupt_count = 0;
|
||||
INIT_COMPLETION(rmp->cb_completion);
|
||||
strlcpy(rmp->remote_item.entries[0].name, "smp2p",
|
||||
SMP2P_MAX_ENTRY_NAME);
|
||||
rmp->remote_item.entries[0].entry = 0;
|
||||
rmp->tx_interrupt();
|
||||
|
||||
local = msm_smp2p_init_rmt_lpb_proc(SMP2P_REMOTE_MOCK_PROC);
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&rmp->cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
|
||||
|
||||
/* Send Echo Command */
|
||||
rmp->rx_interrupt_count = 0;
|
||||
INIT_COMPLETION(rmp->cb_completion);
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_ECHO);
|
||||
SMP2P_SET_RMT_DATA(test_request, 10);
|
||||
rmp->remote_item.entries[0].entry = test_request;
|
||||
rmp->tx_interrupt();
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&rmp->cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
|
||||
/* Verify Echo Response */
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
ret = msm_smp2p_out_read(local,
|
||||
&test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
test_response = SMP2P_GET_RMT_DATA(test_response);
|
||||
UT_ASSERT_INT(test_response, ==, 10);
|
||||
|
||||
/* Send PINGPONG command */
|
||||
test_request = 0;
|
||||
test_response = 0;
|
||||
rmp->rx_interrupt_count = 0;
|
||||
INIT_COMPLETION(rmp->cb_completion);
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_PINGPONG);
|
||||
SMP2P_SET_RMT_DATA(test_request, 10);
|
||||
rmp->remote_item.entries[0].entry = test_request;
|
||||
rmp->tx_interrupt();
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&rmp->cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
|
||||
/* Verify PINGPONG Response */
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
ret = msm_smp2p_out_read(local, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
test_response = SMP2P_GET_RMT_DATA(test_response);
|
||||
UT_ASSERT_INT(test_response, ==, 9);
|
||||
|
||||
/* Send CLEARALL command */
|
||||
test_request = 0;
|
||||
test_response = 0;
|
||||
rmp->rx_interrupt_count = 0;
|
||||
INIT_COMPLETION(rmp->cb_completion);
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_CLEARALL);
|
||||
SMP2P_SET_RMT_DATA(test_request, 10);
|
||||
rmp->remote_item.entries[0].entry = test_request;
|
||||
rmp->tx_interrupt();
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&rmp->cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
|
||||
/* Verify CLEARALL response */
|
||||
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
|
||||
ret = msm_smp2p_out_read(local, &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
test_response = SMP2P_GET_RMT_DATA(test_response);
|
||||
UT_ASSERT_INT(test_response, ==, 0);
|
||||
|
||||
ret = msm_smp2p_deinit_rmt_lpb_proc(SMP2P_REMOTE_MOCK_PROC);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
msm_smp2p_deinit_rmt_lpb_proc(SMP2P_REMOTE_MOCK_PROC);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_inout_core - Verify inbound/outbound functionality.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
* @remote_pid: Remote processor to test
|
||||
*
|
||||
* This test verifies inbound/outbound functionality for the remote processor.
|
||||
*/
|
||||
static void smp2p_ut_remote_inout_core(struct seq_file *s, int remote_pid)
|
||||
{
|
||||
int failed = 0;
|
||||
struct msm_smp2p_out *handle;
|
||||
int ret;
|
||||
uint32_t test_request;
|
||||
uint32_t test_response = 0;
|
||||
static struct mock_cb_data cb_out;
|
||||
static struct mock_cb_data cb_in;
|
||||
|
||||
seq_printf(s, "Running %s for '%s' remote pid %d\n",
|
||||
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
|
||||
mock_cb_data_init(&cb_out);
|
||||
mock_cb_data_init(&cb_in);
|
||||
do {
|
||||
/* Open output entry */
|
||||
ret = msm_smp2p_out_open(remote_pid, "smp2p",
|
||||
&cb_out.nb, &handle);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_out.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_out.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_out.event_open, ==, 1);
|
||||
|
||||
/* Open inbound entry */
|
||||
ret = msm_smp2p_in_register(remote_pid, "smp2p",
|
||||
&cb_in.nb);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in.event_open, ==, 1);
|
||||
|
||||
/* Write an echo request */
|
||||
mock_cb_data_reset(&cb_out);
|
||||
mock_cb_data_reset(&cb_in);
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_ECHO);
|
||||
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
/* Verify inbound reply */
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
|
||||
UT_ASSERT_INT(SMP2P_GET_RMT_DATA(
|
||||
cb_in.entry_data.current_value), ==, 0xAA55);
|
||||
|
||||
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(0, ==, SMP2P_GET_RMT_CMD_TYPE(test_response));
|
||||
UT_ASSERT_INT(SMP2P_LB_CMD_ECHO, ==,
|
||||
SMP2P_GET_RMT_CMD(test_response));
|
||||
UT_ASSERT_INT(0xAA55, ==, SMP2P_GET_RMT_DATA(test_response));
|
||||
|
||||
/* Write a clear all request */
|
||||
mock_cb_data_reset(&cb_in);
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_CLEARALL);
|
||||
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
/* Verify inbound reply */
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
|
||||
UT_ASSERT_INT(SMP2P_GET_RMT_DATA(
|
||||
cb_in.entry_data.current_value), ==, 0x0000);
|
||||
|
||||
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(0, ==, SMP2P_GET_RMT_CMD_TYPE(test_response));
|
||||
UT_ASSERT_INT(0x0000, ==, SMP2P_GET_RMT_DATA(test_response));
|
||||
|
||||
/* Write a decrement request */
|
||||
mock_cb_data_reset(&cb_in);
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_PINGPONG);
|
||||
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
/* Verify inbound reply */
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
|
||||
UT_ASSERT_INT(SMP2P_GET_RMT_DATA(
|
||||
cb_in.entry_data.current_value), ==, 0xAA54);
|
||||
|
||||
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(0, ==, SMP2P_GET_RMT_CMD_TYPE(test_response));
|
||||
UT_ASSERT_INT(SMP2P_LB_CMD_PINGPONG, ==,
|
||||
SMP2P_GET_RMT_CMD(test_response));
|
||||
UT_ASSERT_INT(0xAA54, ==, SMP2P_GET_RMT_DATA(test_response));
|
||||
|
||||
/* Test the ignore flag */
|
||||
mock_cb_data_reset(&cb_in);
|
||||
test_request = 0x0;
|
||||
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
|
||||
SMP2P_SET_RMT_CMD(test_request, SMP2P_RLPB_IGNORE);
|
||||
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
|
||||
ret = msm_smp2p_out_write(handle, test_request);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&cb_in.cb_completion, HZ / 2),
|
||||
==, 0);
|
||||
UT_ASSERT_INT(cb_in.cb_count, ==, 0);
|
||||
UT_ASSERT_INT(cb_in.event_entry_update, ==, 0);
|
||||
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(0xAA54, ==, SMP2P_GET_RMT_DATA(test_response));
|
||||
|
||||
/* Cleanup */
|
||||
ret = msm_smp2p_out_close(&handle);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_PTR(handle, ==, 0);
|
||||
ret = msm_smp2p_in_unregister(remote_pid, "smp2p", &cb_in.nb);
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
if (handle)
|
||||
(void)msm_smp2p_out_close(&handle);
|
||||
(void)msm_smp2p_in_unregister(remote_pid, "smp2p", &cb_in.nb);
|
||||
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_inout - Verify inbound/outbound functionality for all.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test verifies inbound and outbound functionality for all
|
||||
* configured remote processor.
|
||||
*/
|
||||
static void smp2p_ut_remote_inout(struct seq_file *s)
|
||||
{
|
||||
struct smp2p_interrupt_config *int_cfg;
|
||||
int pid;
|
||||
|
||||
int_cfg = smp2p_get_interrupt_config();
|
||||
if (!int_cfg) {
|
||||
seq_printf(s,
|
||||
"Remote processor config unavailable\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
|
||||
if (!int_cfg[pid].is_configured)
|
||||
continue;
|
||||
|
||||
msm_smp2p_deinit_rmt_lpb_proc(pid);
|
||||
smp2p_ut_remote_inout_core(s, pid);
|
||||
msm_smp2p_init_rmt_lpb_proc(pid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_out_max_entries_core - Verify open functionality.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
* @remote_pid: Remote processor for which the test is executed.
|
||||
*
|
||||
* This test verifies open functionality by creating maximum outbound entries.
|
||||
*/
|
||||
static void smp2p_ut_remote_out_max_entries_core(struct seq_file *s,
|
||||
int remote_pid)
|
||||
{
|
||||
int j = 0;
|
||||
int failed = 0;
|
||||
struct msm_smp2p_out *handle[SMP2P_MAX_ENTRY];
|
||||
int ret;
|
||||
static struct mock_cb_data cb_out[SMP2P_MAX_ENTRY];
|
||||
char entry_name[SMP2P_MAX_ENTRY_NAME];
|
||||
int num_created;
|
||||
|
||||
seq_printf(s, "Running %s for '%s' remote pid %d\n",
|
||||
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
|
||||
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
|
||||
handle[j] = NULL;
|
||||
mock_cb_data_init(&cb_out[j]);
|
||||
}
|
||||
|
||||
do {
|
||||
num_created = 0;
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
|
||||
/* Open as many output entries as possible */
|
||||
scnprintf((char *)entry_name, SMP2P_MAX_ENTRY_NAME,
|
||||
"smp2p%d", j);
|
||||
ret = msm_smp2p_out_open(remote_pid, entry_name,
|
||||
&cb_out[j].nb, &handle[j]);
|
||||
if (ret == -ENOMEM)
|
||||
/* hit max number */
|
||||
break;
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
++num_created;
|
||||
}
|
||||
if (failed)
|
||||
break;
|
||||
|
||||
/* verify we created more than 1 entry */
|
||||
UT_ASSERT_INT(num_created, <=, SMP2P_MAX_ENTRY);
|
||||
UT_ASSERT_INT(num_created, >, 0);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++)
|
||||
ret = msm_smp2p_out_close(&handle[j]);
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_remote_out_max_entries - Verify open for all configured processors.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test verifies creating max number of entries for
|
||||
* all configured remote processor.
|
||||
*/
|
||||
static void smp2p_ut_remote_out_max_entries(struct seq_file *s)
|
||||
{
|
||||
struct smp2p_interrupt_config *int_cfg;
|
||||
int pid;
|
||||
|
||||
int_cfg = smp2p_get_interrupt_config();
|
||||
if (!int_cfg) {
|
||||
seq_printf(s,
|
||||
"Remote processor config unavailable\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
|
||||
if (!int_cfg[pid].is_configured)
|
||||
continue;
|
||||
|
||||
smp2p_ut_remote_out_max_entries_core(s, pid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* smp2p_ut_local_in_max_entries - Verify registering and unregistering.
|
||||
*
|
||||
* @s: pointer to output file
|
||||
*
|
||||
* This test verifies registering and unregistering for inbound entries using
|
||||
* the remote mock processor.
|
||||
*/
|
||||
static void smp2p_ut_local_in_max_entries(struct seq_file *s)
|
||||
{
|
||||
int j = 0;
|
||||
int failed = 0;
|
||||
struct msm_smp2p_remote_mock *rmp = NULL;
|
||||
int ret;
|
||||
static struct mock_cb_data cb_in[SMP2P_MAX_ENTRY];
|
||||
static struct mock_cb_data cb_out;
|
||||
|
||||
seq_printf(s, "Running %s\n", __func__);
|
||||
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++)
|
||||
mock_cb_data_init(&cb_in[j]);
|
||||
|
||||
mock_cb_data_init(&cb_out);
|
||||
|
||||
do {
|
||||
/* Initialize mock edge */
|
||||
ret = smp2p_reset_mock_edge();
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
|
||||
rmp = msm_smp2p_get_remote_mock();
|
||||
UT_ASSERT_PTR(rmp, !=, NULL);
|
||||
|
||||
rmp->rx_interrupt_count = 0;
|
||||
memset(&rmp->remote_item, 0,
|
||||
sizeof(struct smp2p_smem_item));
|
||||
rmp->remote_item.header.magic = SMP2P_MAGIC;
|
||||
SMP2P_SET_LOCAL_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_REMOTE_MOCK_PROC);
|
||||
SMP2P_SET_REMOTE_PID(
|
||||
rmp->remote_item.header.rem_loc_proc_id,
|
||||
SMP2P_APPS_PROC);
|
||||
SMP2P_SET_VERSION(
|
||||
rmp->remote_item.header.feature_version, 1);
|
||||
SMP2P_SET_FEATURES(
|
||||
rmp->remote_item.header.feature_version, 0);
|
||||
SMP2P_SET_ENT_TOTAL(
|
||||
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
|
||||
SMP2P_SET_ENT_VALID(
|
||||
rmp->remote_item.header.valid_total_ent, 0);
|
||||
rmp->remote_item.header.reserved = 0x0;
|
||||
msm_smp2p_set_remote_mock_exists(true);
|
||||
|
||||
/* Create Max Entries in the remote mock object */
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
|
||||
scnprintf(rmp->remote_item.entries[j].name,
|
||||
SMP2P_MAX_ENTRY_NAME, "smp2p%d", j);
|
||||
rmp->remote_item.entries[j].entry = 0;
|
||||
rmp->tx_interrupt();
|
||||
}
|
||||
|
||||
/* Register for in entries */
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
|
||||
ret = msm_smp2p_in_register(SMP2P_REMOTE_MOCK_PROC,
|
||||
rmp->remote_item.entries[j].name,
|
||||
&(cb_in[j].nb));
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
UT_ASSERT_INT(
|
||||
(int)wait_for_completion_timeout(
|
||||
&(cb_in[j].cb_completion), HZ / 2),
|
||||
>, 0);
|
||||
UT_ASSERT_INT(cb_in[j].cb_count, ==, 1);
|
||||
UT_ASSERT_INT(cb_in[j].event_entry_update, ==, 0);
|
||||
}
|
||||
UT_ASSERT_INT(j, ==, SMP2P_MAX_ENTRY);
|
||||
|
||||
/* Unregister */
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
|
||||
ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC,
|
||||
rmp->remote_item.entries[j].name,
|
||||
&(cb_in[j].nb));
|
||||
UT_ASSERT_INT(ret, ==, 0);
|
||||
}
|
||||
UT_ASSERT_INT(j, ==, SMP2P_MAX_ENTRY);
|
||||
|
||||
seq_printf(s, "\tOK\n");
|
||||
} while (0);
|
||||
|
||||
if (failed) {
|
||||
pr_err("%s: Failed\n", __func__);
|
||||
seq_printf(s, "\tFailed\n");
|
||||
|
||||
for (j = 0; j < SMP2P_MAX_ENTRY; j++)
|
||||
ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC,
|
||||
rmp->remote_item.entries[j].name,
|
||||
&(cb_in[j].nb));
|
||||
}
|
||||
}
|
||||
|
||||
static struct dentry *dent;
|
||||
|
||||
static int debugfs_show(struct seq_file *s, void *data)
|
||||
{
|
||||
void (*show)(struct seq_file *) = s->private;
|
||||
|
||||
show(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, debugfs_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations debug_ops = {
|
||||
.open = debug_open,
|
||||
.release = single_release,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
};
|
||||
|
||||
void smp2p_debug_create(const char *name,
|
||||
void (*show)(struct seq_file *))
|
||||
{
|
||||
struct dentry *file;
|
||||
|
||||
file = debugfs_create_file(name, 0444, dent, show, &debug_ops);
|
||||
if (!file)
|
||||
pr_err("%s: unable to create file '%s'\n", __func__, name);
|
||||
}
|
||||
|
||||
static int __init smp2p_debugfs_init(void)
|
||||
{
|
||||
dent = debugfs_create_dir("smp2p_test", 0);
|
||||
if (IS_ERR(dent))
|
||||
return PTR_ERR(dent);
|
||||
|
||||
/*
|
||||
* Add Unit Test entries.
|
||||
*
|
||||
* The idea with unit tests is that you can run all of them
|
||||
* from ADB shell by doing:
|
||||
* adb shell
|
||||
* cat ut*
|
||||
*
|
||||
* And if particular tests fail, you can then repeatedly run the
|
||||
* failing tests as you debug and resolve the failing test.
|
||||
*/
|
||||
smp2p_debug_create("ut_local_basic",
|
||||
smp2p_ut_local_basic);
|
||||
smp2p_debug_create("ut_local_late_open",
|
||||
smp2p_ut_local_late_open);
|
||||
smp2p_debug_create("ut_local_early_open",
|
||||
smp2p_ut_local_early_open);
|
||||
smp2p_debug_create("ut_mock_loopback",
|
||||
smp2p_ut_mock_loopback);
|
||||
smp2p_debug_create("ut_remote_inout",
|
||||
smp2p_ut_remote_inout);
|
||||
smp2p_debug_create("ut_local_in_max_entries",
|
||||
smp2p_ut_local_in_max_entries);
|
||||
smp2p_debug_create("ut_remote_out_max_entries",
|
||||
smp2p_ut_remote_out_max_entries);
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(smp2p_debugfs_init);
|
|
@ -0,0 +1,217 @@
|
|||
/* arch/arm/mach-msm/smp2p_test_common.h
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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.
|
||||
*/
|
||||
#ifndef _ARCH_ARM_MACH_MSM_SMP2P_TEST_COMMON_H_
|
||||
#define _ARCH_ARM_MACH_MSM_SMP2P_TEST_COMMON_H_
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
/**
|
||||
* Unit test assertion for logging test cases.
|
||||
*
|
||||
* @a lval
|
||||
* @b rval
|
||||
* @cmp comparison operator
|
||||
*
|
||||
* Assertion fails if (@a cmp @b) is not true which then
|
||||
* logs the function and line number where the error occurred
|
||||
* along with the values of @a and @b.
|
||||
*
|
||||
* Assumes that the following local variables exist:
|
||||
* @s - sequential output file pointer
|
||||
* @failed - set to true if test fails
|
||||
*/
|
||||
#define UT_ASSERT_INT(a, cmp, b) \
|
||||
{ \
|
||||
int a_tmp = (a); \
|
||||
int b_tmp = (b); \
|
||||
if (!((a_tmp)cmp(b_tmp))) { \
|
||||
seq_printf(s, \
|
||||
"%s:%d Fail: " #a "(%d) " #cmp " " #b "(%d)\n", \
|
||||
__func__, __LINE__, \
|
||||
a_tmp, b_tmp); \
|
||||
failed = 1; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define UT_ASSERT_PTR(a, cmp, b) \
|
||||
{ \
|
||||
void *a_tmp = (a); \
|
||||
void *b_tmp = (b); \
|
||||
if (!((a_tmp)cmp(b_tmp))) { \
|
||||
seq_printf(s, \
|
||||
"%s:%d Fail: " #a "(%p) " #cmp " " #b "(%p)\n", \
|
||||
__func__, __LINE__, \
|
||||
a_tmp, b_tmp); \
|
||||
failed = 1; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define UT_ASSERT_UINT(a, cmp, b) \
|
||||
{ \
|
||||
unsigned a_tmp = (a); \
|
||||
unsigned b_tmp = (b); \
|
||||
if (!((a_tmp)cmp(b_tmp))) { \
|
||||
seq_printf(s, \
|
||||
"%s:%d Fail: " #a "(%u) " #cmp " " #b "(%u)\n", \
|
||||
__func__, __LINE__, \
|
||||
a_tmp, b_tmp); \
|
||||
failed = 1; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define UT_ASSERT_HEX(a, cmp, b) \
|
||||
{ \
|
||||
unsigned a_tmp = (a); \
|
||||
unsigned b_tmp = (b); \
|
||||
if (!((a_tmp)cmp(b_tmp))) { \
|
||||
seq_printf(s, \
|
||||
"%s:%d Fail: " #a "(%x) " #cmp " " #b "(%x)\n", \
|
||||
__func__, __LINE__, \
|
||||
a_tmp, b_tmp); \
|
||||
failed = 1; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* In-range unit test assertion for test cases.
|
||||
*
|
||||
* @a lval
|
||||
* @minv Minimum value
|
||||
* @maxv Maximum value
|
||||
*
|
||||
* Assertion fails if @a is not on the exclusive range minv, maxv
|
||||
* ((@a < @minv) or (@a > @maxv)). In the failure case, the macro
|
||||
* logs the function and line number where the error occurred along
|
||||
* with the values of @a and @minv, @maxv.
|
||||
*
|
||||
* Assumes that the following local variables exist:
|
||||
* @s - sequential output file pointer
|
||||
* @failed - set to true if test fails
|
||||
*/
|
||||
#define UT_ASSERT_INT_IN_RANGE(a, minv, maxv) \
|
||||
{ \
|
||||
int a_tmp = (a); \
|
||||
int minv_tmp = (minv); \
|
||||
int maxv_tmp = (maxv); \
|
||||
if (((a_tmp) < (minv_tmp)) || ((a_tmp) > (maxv_tmp))) { \
|
||||
seq_printf(s, \
|
||||
"%s:%d Fail: " #a "(%d) < " #minv "(%d) or " \
|
||||
#a "(%d) > " #maxv "(%d)\n", \
|
||||
__func__, __LINE__, \
|
||||
a_tmp, minv_tmp, a_tmp, maxv_tmp); \
|
||||
failed = 1; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
/* Structure to track state changes for the notifier callback. */
|
||||
struct mock_cb_data {
|
||||
bool initialized;
|
||||
spinlock_t lock;
|
||||
struct notifier_block nb;
|
||||
|
||||
/* events */
|
||||
struct completion cb_completion;
|
||||
int cb_count;
|
||||
int event_open;
|
||||
int event_entry_update;
|
||||
struct msm_smp2p_update_notif entry_data;
|
||||
};
|
||||
|
||||
void smp2p_debug_create(const char *name, void (*show)(struct seq_file *));
|
||||
static inline int smp2p_test_notify(struct notifier_block *self,
|
||||
unsigned long event, void *data);
|
||||
|
||||
/**
|
||||
* Reset mock callback data to default values.
|
||||
*
|
||||
* @cb: Mock callback data
|
||||
*/
|
||||
static inline void mock_cb_data_reset(struct mock_cb_data *cb)
|
||||
{
|
||||
INIT_COMPLETION(cb->cb_completion);
|
||||
cb->cb_count = 0;
|
||||
cb->event_open = 0;
|
||||
cb->event_entry_update = 0;
|
||||
memset(&cb->entry_data, 0,
|
||||
sizeof(struct msm_smp2p_update_notif));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize mock callback data.
|
||||
*
|
||||
* @cb: Mock callback data
|
||||
*/
|
||||
static inline void mock_cb_data_init(struct mock_cb_data *cb)
|
||||
{
|
||||
if (!cb->initialized) {
|
||||
init_completion(&cb->cb_completion);
|
||||
spin_lock_init(&cb->lock);
|
||||
cb->initialized = true;
|
||||
cb->nb.notifier_call = smp2p_test_notify;
|
||||
memset(&cb->entry_data, 0,
|
||||
sizeof(struct msm_smp2p_update_notif));
|
||||
}
|
||||
mock_cb_data_reset(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifier function passed into SMP2P for testing.
|
||||
*
|
||||
* @self: Pointer to calling notifier block
|
||||
* @event: Event
|
||||
* @data: Event-specific data
|
||||
* @returns: 0
|
||||
*/
|
||||
static inline int smp2p_test_notify(struct notifier_block *self,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct mock_cb_data *cb_data_ptr;
|
||||
unsigned long flags;
|
||||
|
||||
cb_data_ptr = container_of(self, struct mock_cb_data, nb);
|
||||
|
||||
spin_lock_irqsave(&cb_data_ptr->lock, flags);
|
||||
|
||||
switch (event) {
|
||||
case SMP2P_OPEN:
|
||||
++cb_data_ptr->event_open;
|
||||
if (data) {
|
||||
cb_data_ptr->entry_data =
|
||||
*(struct msm_smp2p_update_notif *)(data);
|
||||
}
|
||||
break;
|
||||
case SMP2P_ENTRY_UPDATE:
|
||||
++cb_data_ptr->event_entry_update;
|
||||
if (data) {
|
||||
cb_data_ptr->entry_data =
|
||||
*(struct msm_smp2p_update_notif *)(data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_err("%s Unknown event\n", __func__);
|
||||
break;
|
||||
}
|
||||
|
||||
++cb_data_ptr->cb_count;
|
||||
complete(&cb_data_ptr->cb_completion);
|
||||
spin_unlock_irqrestore(&cb_data_ptr->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
#endif /* _ARCH_ARM_MACH_MSM_SMP2P_TEST_COMMON_H_ */
|
Loading…
Reference in New Issue