soc: qcom: Add snapshot of SMP2P Driver

This snapshot is taken as of msm-3.18 commit e70ad0cd (Promotion of
kernel.lnx.3.18-151201.)

Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
This commit is contained in:
Karthikeyan Ramasubramanian 2016-01-22 16:27:03 -07:00 committed by David Keitel
parent 180a1bcbe0
commit d4516a3652
16 changed files with 7695 additions and 0 deletions

View file

@ -0,0 +1,476 @@
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 |
| section for details. |
| bit 0 SSR_ACK Feature Supported when set to 1 |
| bits 1:31 Reserved Must be set to 0. |
---------------------------------------------------------------------------
| 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. |
---------------------------------------------------------------------------
| Flags 4 Bytes |
| bit 0 RESTART_DONE Toggle for every restart |
| bit 1 RESTART_ACK Toggle to ACK remote |
| RESTART_DONE |
| bits 2:31 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 |
-------------------------------------------------
| TrustZone processor | 7 |
-------------------------------------------------
| NUM PROCESSORS | 8 |
-------------------------------------------------
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 |
---------------------------------------------------
| Sensors SMP2P SMEM Item base | 481 |
---------------------------------------------------
| Wireless SMP2P SMEM Item base | 451 |
---------------------------------------------------
| Power SMP2P SMEM Item base | 459 |
---------------------------------------------------
| TrustZone SMP2P SMEM Item base | 489 |
---------------------------------------------------
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.
Security
========
Since the implementation resides in the kernel and does not expose interfaces
to userspace, no security issues are anticipated. The usage of separate SMEM
items allows for future security enhancements in SMEM.
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 for the GPIO interface are contained in
the file Documentation/devicetree/bindings/gpio/gpio-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.
Subsystem Restart
=================
SMP2P is unaffected by SubSystem Restart (SSR) on the high-level OS side and is
actually used as an underlying communication mechanism for SSR. On the
peripheral system that is being restarted, SMP2P will zero out all existing
state entries upon reboot as part of the SMP2P initialization process and if the
SSR_ACK feature is enabled, then it waits for an acknowledgement as outlined in
the following subsections.
SSR_ACK Feature - Reboot Use Case (Non-HLOS Only)
-------------------------------------------------
If a remote system boots up after an SSR and sees that the remote and local
version numbers and feature flags are equal, then it zeros out entry values. If
the SSR_ACK feature is enabled, it will wait for an acknowledgement from the other
processor that it has seen the zero entry before completing the negotiation
sequence.
if remote and local version numbers and feature flags are equal
Zero out all entry values
if SSR_ACK feature is enabled
Set local RESTART_DONE flag to inverse of the remote RESTART_ACK
Send interrupt to remote system
Wait for interrupt and for remote RESTART_ACK to be equal to local
RESTART_DONE
Continue with normal negotiation sequence
Interrupt Use Case
------------------
For every interrupt triggered by a remote change, SMP2P will notify the client
of a change in state. In addition, if the SSR_ACK feature is enabled, the SSR
handshaking will also be handled.
if SSR_ACK feature is enabled
if remote RESTART_DONE != local RESTART_ACK
Notify client of entry change (will be * -> 0 transition)
Toggle local RESTART_ACK
Send interrupt to remote system
else
Notify client of entry change as usual
else
Notify client of entry change as usual
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 per the format in
Documentation/devicetree/bindings/arm/msm/smp2p.txt. 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
component for size optimization.
CONFIG_MSM_SMP2P - enables SMP2P
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.

View file

@ -0,0 +1,93 @@
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>
Unit test devices ("smp2p" entries):
-compatible : should be one of
"qcom,smp2pgpio_test_smp2p_1_out"
"qcom,smp2pgpio_test_smp2p_1_in"
"qcom,smp2pgpio_test_smp2p_2_out"
"qcom,smp2pgpio_test_smp2p_2_in"
"qcom,smp2pgpio_test_smp2p_3_out"
"qcom,smp2pgpio_test_smp2p_3_in"
"qcom,smp2pgpio_test_smp2p_4_out"
"qcom,smp2pgpio_test_smp2p_4_in"
"qcom,smp2pgpio_test_smp2p_7_out"
"qcom,smp2pgpio_test_smp2p_7_in"
"qcom,smp2pgpio_test_smp2p_15_out"
"qcom,smp2pgpio_test_smp2p_15_in"
-gpios : the relevant gpio pins of the entry
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 */
};

View file

@ -120,3 +120,5 @@ obj-$(CONFIG_GPIO_XTENSA) += gpio-xtensa.o
obj-$(CONFIG_GPIO_ZEVIO) += gpio-zevio.o
obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o
obj-$(CONFIG_GPIO_ZX) += gpio-zx.o
obj-$(CONFIG_MSM_SMP2P) += gpio-msm-smp2p.o
obj-$(CONFIG_MSM_SMP2P_TEST) += gpio-msm-smp2p-test.o

View file

@ -0,0 +1,754 @@
/* drivers/gpio/gpio-msm-smp2p-test.c
*
* Copyright (c) 2013-2015, 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/interrupt.h>
#include <linux/bitmap.h>
#include "../soc/qcom/smp2p_private.h"
#include "../soc/qcom/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);
reinit_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_3_in", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_SENSOR_PROC].in;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_3_out", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_SENSOR_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_TZ_PROC].in;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_7_out", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_TZ_PROC].out;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_15_in", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
} else if (
strcmp("qcom,smp2pgpio_test_smp2p_15_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);
if (id == -EPROBE_DEFER)
return id;
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", },
/* sensor */
{.compatible = "qcom,smp2pgpio_test_smp2p_3_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_3_in", },
/* wcnss */
{.compatible = "qcom,smp2pgpio_test_smp2p_4_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_4_in", },
/* TZ */
{.compatible = "qcom,smp2pgpio_test_smp2p_7_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_7_in", },
/* mock loopback */
{.compatible = "qcom,smp2pgpio_test_smp2p_15_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_15_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_puts(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_puts(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_PTR(NULL, !=, 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_puts(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_puts(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_ut_local_gpio_in_update_open - Verify combined open/update.
*
* @s: pointer to output file
*
* If the remote side updates the SMP2P bits and sends before negotiation is
* complete, then the UPDATE event will have to be delayed until negotiation is
* complete. This should result in both the OPEN and UPDATE events coming in
* right after each other and the behavior should be transparent to the clients
* of SMP2P GPIO.
*/
static void smp2p_ut_local_gpio_in_update_open(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);
/* register for interrupts */
smp2p_gpio_open_test_entry("smp2p",
SMP2P_REMOTE_MOCK_PROC, true);
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_PTR(NULL, !=, irq_to_desc(virq));
ret = request_irq(virq,
smp2p_gpio_irq, IRQ_TYPE_EDGE_BOTH,
"smp2p_test", cb_info);
UT_ASSERT_INT(0, ==, ret);
}
if (failed)
break;
/* update the state value and complete negotiation */
mock->remote_item.entries[0].entry = 0xDEADDEAD;
msm_smp2p_set_remote_mock_exists(true);
mock->tx_interrupt();
/* verify delayed state updates were processed */
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
virq = cb_info->irq_base_id + id;
UT_ASSERT_INT(cb_info->cb_count, >, 0);
if (0x1 & (0xDEADDEAD >> id)) {
/* rising edge should have been triggered */
if (!test_bit(id, cb_info->triggered_irqs)) {
seq_printf(s, "%s:%d bit %d clear, ",
__func__, __LINE__, id);
seq_puts(s, "expected set\n");
failed = 1;
break;
}
} else {
/* edge should not have been triggered */
if (test_bit(id, cb_info->triggered_irqs)) {
seq_printf(s, "%s:%d bit %d set, ",
__func__, __LINE__, id);
seq_puts(s, "expected clear\n");
failed = 1;
break;
}
}
}
if (failed)
break;
seq_puts(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_puts(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_PTR(NULL, !=, 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;
reinit_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);
reinit_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_puts(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", name);
seq_puts(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_puts(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_local_gpio_in_update_open",
smp2p_ut_local_gpio_in_update_open);
smp2p_debug_create("ut_remote_gpio_inout", smp2p_ut_remote_inout);
return 0;
}
late_initcall(smp2p_debugfs_init);

View file

@ -0,0 +1,836 @@
/* drivers/gpio/gpio-msm-smp2p.c
*
* Copyright (c) 2013-2014, 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/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/ipc_logging.h>
#include "../soc/qcom/smp2p_private_api.h"
#include "../soc/qcom/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;
bool in_shadow;
uint32_t shadow_value;
struct work_struct shadow_work;
spinlock_t shadow_lock;
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;
bool send_irq;
int ret;
unsigned long flags;
if (!cp)
return;
chip = container_of(cp, struct smp2p_chip_dev, gpio);
if (chip->is_inbound) {
SMP2P_INFO("%s: '%s':%d virq %d invalid operation\n",
__func__, chip->name, chip->remote_pid,
chip->irq_base + offset);
return;
}
if (value & SMP2P_GPIO_NO_INT) {
value &= ~SMP2P_GPIO_NO_INT;
send_irq = false;
} else {
send_irq = true;
}
if (value) {
data_set = 1 << offset;
data_clear = 0;
} else {
data_set = 0;
data_clear = 1 << offset;
}
spin_lock_irqsave(&chip->shadow_lock, flags);
if (!chip->is_open) {
chip->in_shadow = true;
chip->shadow_value &= ~data_clear;
chip->shadow_value |= data_set;
spin_unlock_irqrestore(&chip->shadow_lock, flags);
return;
}
if (chip->in_shadow) {
chip->in_shadow = false;
chip->shadow_value &= ~data_clear;
chip->shadow_value |= data_set;
ret = msm_smp2p_out_modify(chip->out_handle,
chip->shadow_value, 0x0, send_irq);
chip->shadow_value = 0x0;
} else {
ret = msm_smp2p_out_modify(chip->out_handle,
data_set, data_clear, send_irq);
}
spin_unlock_irqrestore(&chip->shadow_lock, flags);
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_INFO(
"'%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 irq_base;
/* map GPIO pins to interrupts */
chip->irq_domain = irq_domain_add_linear(node, SMP2P_BITS_PER_ENTRY,
&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);
goto domain_fail;
}
/* alloc a contiguous set of virt irqs from anywhere in the irq space */
irq_base = irq_alloc_descs_from(0, SMP2P_BITS_PER_ENTRY,
of_node_to_nid(chip->irq_domain->of_node));
if (irq_base < 0) {
SMP2P_ERR("alloc virt irqs failed:%d name:%s pid%d\n", irq_base,
chip->name, chip->remote_pid);
goto irq_alloc_fail;
}
/* map the allocated irqs to gpios */
irq_domain_associate_many(chip->irq_domain, irq_base, 0,
SMP2P_BITS_PER_ENTRY);
chip->irq_base = irq_base;
SMP2P_DBG("create mapping:%d naem:%s pid:%d\n", chip->irq_base,
chip->name, chip->remote_pid);
return;
irq_alloc_fail:
irq_domain_remove(chip->irq_domain);
domain_fail:
return;
}
/**
* 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 in_shadow[%d]\n", __func__,
chip->name, chip->remote_pid, chip->in_shadow);
if (chip->in_shadow)
schedule_work(&chip->shadow_work);
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;
}
/**
* smp2p_gpio_shadow_worker - Handles shadow updates of an entry.
*
* @work: Work Item scheduled to handle the shadow updates.
*/
static void smp2p_gpio_shadow_worker(struct work_struct *work)
{
struct smp2p_chip_dev *chip;
int ret;
unsigned long flags;
chip = container_of(work, struct smp2p_chip_dev, shadow_work);
spin_lock_irqsave(&chip->shadow_lock, flags);
if (chip->in_shadow) {
ret = msm_smp2p_out_modify(chip->out_handle,
chip->shadow_value, 0x0, true);
if (ret)
SMP2P_GPIO("'%s':%d shadow val[0x%x] failed(%d)\n",
chip->name, chip->remote_pid,
chip->shadow_value, ret);
else
SMP2P_GPIO("'%s':%d shadow val[0x%x]\n",
chip->name, chip->remote_pid,
chip->shadow_value);
chip->shadow_value = 0;
chip->in_shadow = false;
}
spin_unlock_irqrestore(&chip->shadow_lock, flags);
}
/**
* 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);
spin_lock_init(&chip->shadow_lock);
INIT_WORK(&chip->shadow_work, smp2p_gpio_shadow_worker);
/* 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 error;
}
} 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 error;
}
}
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;
error:
gpiochip_remove(&chip->gpio);
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");

View file

@ -70,6 +70,27 @@ config MSM_GLINK_SMEM_NATIVE_XPRT
transport to only connecting with entities internal to the
System-on-Chip.
config MSM_SMP2P
bool "SMSM Point-to-Point (SMP2P)"
depends on MSM_SMEM
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_RPM_SMD
bool "RPM driver using SMD protocol"
help

View file

@ -5,6 +5,8 @@ obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o
obj-$(CONFIG_MSM_GLINK_SMD_XPRT) += glink_smd_xprt.o
obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT)+= glink_smem_native_xprt.o
obj-$(CONFIG_ARCH_QCOM) += kryo-l2-accessors.o
obj-$(CONFIG_MSM_SMP2P) += smp2p.o smp2p_debug.o smp2p_sleepstate.o
obj-$(CONFIG_MSM_SMP2P_TEST) += smp2p_loopback.o smp2p_test.o smp2p_spinlock_test.o
obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd.o
ifdef CONFIG_DEBUG_FS
obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o

1951
drivers/soc/qcom/smp2p.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,335 @@
/* drivers/soc/qcom/smp2p_debug.c
*
* Copyright (c) 2013-2014, 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 <linux/io.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_puts(s, "| Processor | Incoming Id | Incoming # |");
seq_puts(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 #/Max: %d/%d Flags: %c%c",
SMP2P_GET_ENT_VALID(item_ptr->valid_total_ent),
SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent),
item_ptr->flags & SMP2P_FLAGS_RESTART_ACK_MASK ? 'A' : 'a',
item_ptr->flags & SMP2P_FLAGS_RESTART_DONE_MASK ? 'D' : 'd'
);
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;
char entry_name[SMP2P_MAX_ENTRY_NAME];
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(in_ptr->valid_total_ent);
}
for (entry = 0; out_entries || in_entries; ++entry) {
if (out_entries && entry < out_valid) {
memcpy_fromio(entry_name, out_entries->name,
SMP2P_MAX_ENTRY_NAME);
scnprintf(tmp_buff, sizeof(tmp_buff),
"%-16s 0x%08x",
entry_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) {
memcpy_fromio(entry_name, in_entries->name,
SMP2P_MAX_ENTRY_NAME);
scnprintf(tmp_buff, sizeof(tmp_buff),
"%-16s 0x%08x",
entry_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 */

View file

@ -0,0 +1,449 @@
/* drivers/soc/qcom/smp2p_loopback.c
*
* Copyright (c) 2013-2014, 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 "smem_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;
struct smp2p_interrupt_config *int_cfg;
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;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg) {
SMP2P_ERR("Remote processor config unavailable\n");
return 0;
}
if (!int_cfg[i].is_configured)
continue;
msm_smp2p_init_rmt_lpb(&remote_loopback[i],
i, SMP2P_RLPB_ENTRY_NAME);
}
return 0;
}
module_init(smp2p_remote_mock_init);

View file

@ -0,0 +1,253 @@
/* drivers/soc/qcom/smp2p_private.h
*
* Copyright (c) 2013-2014, 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 <linux/ipc_logging.h>
#include "smp2p_private_api.h"
#define SMP2P_MAX_ENTRY 16
#define SMP2P_FEATURE_SSR_ACK 0x1
/* 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_FLAGS_RESTART_DONE_BIT 0
#define SMP2P_FLAGS_RESTART_DONE_MASK 0x1
#define SMP2P_FLAGS_RESTART_ACK_BIT 1
#define SMP2P_FLAGS_RESTART_ACK_MASK 0x2
#define SMP2P_GPIO_NO_INT BIT(1)
#define SMP2P_GET_BITS(hdr_val, mask, bit) \
(((hdr_val) & (mask)) >> (bit))
#define SMP2P_SET_BITS(hdr_val, mask, bit, new_value) \
{\
hdr_val = (hdr_val & ~(mask)) \
| (((new_value) << (bit)) & (mask)); \
}
#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)
#define SMP2P_GET_RESTART_DONE(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_FLAGS_RESTART_DONE_MASK, \
SMP2P_FLAGS_RESTART_DONE_BIT)
#define SMP2P_SET_RESTART_DONE(hdr, value) \
SMP2P_SET_BITS(hdr, SMP2P_FLAGS_RESTART_DONE_MASK, \
SMP2P_FLAGS_RESTART_DONE_BIT, value)
#define SMP2P_GET_RESTART_ACK(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_FLAGS_RESTART_ACK_MASK, \
SMP2P_FLAGS_RESTART_ACK_BIT)
#define SMP2P_SET_RESTART_ACK(hdr, value) \
SMP2P_SET_BITS(hdr, SMP2P_FLAGS_RESTART_ACK_MASK, \
SMP2P_FLAGS_RESTART_ACK_BIT, value)
/* 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 - SMP2P SMEM Item Header
*
* @magic: Set to "$SMP" -- used for identification / debug purposes
* @feature_version: Feature and version fields
* @rem_loc_proc_id: Remote (31:16) and Local (15:0) processor IDs
* @valid_total_ent: Valid (31:16) and total (15:0) entries
* @flags: Flags (bits 31:2 reserved)
*/
struct smp2p_smem {
uint32_t magic;
uint32_t feature_version;
uint32_t rem_loc_proc_id;
uint32_t valid_total_ent;
uint32_t flags;
};
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

View file

@ -0,0 +1,80 @@
/* drivers/soc/qcom/smp2p_private_api.h
*
* Copyright (c) 2013-2015, 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_SENSOR_PROC = 3,
SMP2P_WIRELESS_PROC = 4,
SMP2P_RESERVED_PROC_2 = 5,
SMP2P_POWER_PROC = 6,
SMP2P_TZ_PROC = 7,
/* add new processors here */
SMP2P_REMOTE_MOCK_PROC = 15,
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, bool send_irq);
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_ */

View file

@ -0,0 +1,104 @@
/* Copyright (c) 2014-2015, 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/gpio.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include "smp2p_private.h"
#define SET_DELAY (2 * HZ)
#define PROC_AWAKE_ID 12 /* 12th bit */
static int slst_gpio_base_id;
/**
* sleepstate_pm_notifier() - PM notifier callback function.
* @nb: Pointer to the notifier block.
* @event: Suspend state event from PM module.
* @unused: Null pointer from PM module.
*
* This function is register as callback function to get notifications
* from the PM module on the system suspend state.
*/
static int sleepstate_pm_notifier(struct notifier_block *nb,
unsigned long event, void *unused)
{
switch (event) {
case PM_SUSPEND_PREPARE:
gpio_set_value(slst_gpio_base_id + PROC_AWAKE_ID, 0);
break;
case PM_POST_SUSPEND:
gpio_set_value(slst_gpio_base_id + PROC_AWAKE_ID, 1);
break;
}
return NOTIFY_DONE;
}
static struct notifier_block sleepstate_pm_nb = {
.notifier_call = sleepstate_pm_notifier,
};
static int smp2p_sleepstate_probe(struct platform_device *pdev)
{
int ret;
struct device_node *node = pdev->dev.of_node;
slst_gpio_base_id = of_get_gpio(node, 0);
if (slst_gpio_base_id == -EPROBE_DEFER) {
return slst_gpio_base_id;
} else if (slst_gpio_base_id < 0) {
SMP2P_ERR("%s: Error to get gpio %d\n",
__func__, slst_gpio_base_id);
return slst_gpio_base_id;
}
gpio_set_value(slst_gpio_base_id + PROC_AWAKE_ID, 1);
ret = register_pm_notifier(&sleepstate_pm_nb);
if (ret)
SMP2P_ERR("%s: power state notif error %d\n", __func__, ret);
return 0;
}
static struct of_device_id msm_smp2p_slst_match_table[] = {
{.compatible = "qcom,smp2pgpio_sleepstate_3_out"},
{},
};
static struct platform_driver smp2p_sleepstate_driver = {
.probe = smp2p_sleepstate_probe,
.driver = {
.name = "smp2p_sleepstate",
.owner = THIS_MODULE,
.of_match_table = msm_smp2p_slst_match_table,
},
};
static int __init smp2p_sleepstate_init(void)
{
int ret;
ret = platform_driver_register(&smp2p_sleepstate_driver);
if (ret) {
SMP2P_ERR("%s: smp2p_sleepstate_driver register failed %d\n",
__func__, ret);
return ret;
}
return 0;
}
module_init(smp2p_sleepstate_init);
MODULE_DESCRIPTION("SMP2P SLEEP STATE");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,804 @@
/* drivers/soc/qcom/smp2p_spinlock_test.c
*
* Copyright (c) 2013-2015, 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 <soc/qcom/smem.h>
#include "smem_private.h"
#include "smp2p_private.h"
#include "smp2p_test_common.h"
#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;
};
static uint32_t ut_remote_spinlock_run_time = 1;
/**
* 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;
bool have_lock;
bool timeout;
int failed_tmp;
int spinlock_owner;
remote_spinlock_t *smem_spinlock;
unsigned long end;
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_puts(s, "\tUsing remote_spin_trylock\n");
else
seq_puts(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);
end = jiffies + (ut_remote_spinlock_run_time * HZ);
if (ut_remote_spinlock_run_time < 300) {
seq_printf(s, "\tRunning test for %u seconds; ",
ut_remote_spinlock_run_time);
seq_puts(s,
"on physical hardware please run >= 300 seconds by doing 'echo 300 > ut_remote_spinlock_time'\n");
}
while (time_is_after_jiffies(end)) {
/* 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_puts(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 != SMEM_APPS) {
/* lock stolen by remote side */
seq_puts(s, "\tFail: Remote side: ");
seq_printf(s, "%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);
reinit_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_puts(s, "\tLocked spinlock ");
seq_printf(s, "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_puts(s, "Remote side reporting lock stolen by ");
seq_printf(s, "pid %d.\n",
SMP2P_GET_BITS(test_response,
RS_END_THIEF_MASK,
RS_END_THIEF_PID_BIT));
failed = 1;
}
seq_puts(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_puts(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_puts(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_puts(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_dsps(struct seq_file *s)
{
smp2p_ut_remote_spinlock_pid(s, SMP2P_SENSOR_PROC, false);
}
static void smp2p_ut_remote_spinlock_wcnss(struct seq_file *s)
{
smp2p_ut_remote_spinlock_pid(s, SMP2P_WIRELESS_PROC, false);
}
static void smp2p_ut_remote_spinlock_tz(struct seq_file *s)
{
smp2p_ut_remote_spinlock_pid(s, SMP2P_TZ_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_alloc(SMEM_ID_VENDOR0,
sizeof(struct rpm_spinlock_test), 0,
SMEM_ANY_HOST_FLAG);
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_puts(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_range(1000, 1200);
}
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;
data_ptr->apps_lock_count++;
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_puts(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_puts(s, "\tFailed\n");
}
}
struct rmt_spinlock_work_item {
struct work_struct work;
struct completion try_lock;
struct completion locked;
bool has_locked;
};
static void ut_remote_spinlock_ssr_worker(struct work_struct *work)
{
remote_spinlock_t *smem_spinlock;
unsigned long flags;
struct rmt_spinlock_work_item *work_item =
container_of(work, struct rmt_spinlock_work_item, work);
work_item->has_locked = false;
complete(&work_item->try_lock);
smem_spinlock = smem_get_remote_spinlock();
if (!smem_spinlock) {
pr_err("%s Failed\n", __func__);
return;
}
remote_spin_lock_irqsave(smem_spinlock, flags);
remote_spin_unlock_irqrestore(smem_spinlock, flags);
work_item->has_locked = true;
complete(&work_item->locked);
}
/**
* smp2p_ut_remote_spinlock_ssr - Verify remote spinlock.
*
* @s: pointer to output file
*/
static void smp2p_ut_remote_spinlock_ssr(struct seq_file *s)
{
int failed = 0;
unsigned long flags;
remote_spinlock_t *smem_spinlock;
int spinlock_owner = 0;
struct workqueue_struct *ws = NULL;
struct rmt_spinlock_work_item work_item;
seq_printf(s, " Running %s Test\n",
__func__);
do {
smem_spinlock = smem_get_remote_spinlock();
UT_ASSERT_PTR(smem_spinlock, !=, NULL);
ws = create_singlethread_workqueue("ut_remote_spinlock_ssr");
UT_ASSERT_PTR(ws, !=, NULL);
INIT_WORK(&work_item.work, ut_remote_spinlock_ssr_worker);
init_completion(&work_item.try_lock);
init_completion(&work_item.locked);
remote_spin_lock_irqsave(smem_spinlock, flags);
/* Unlock local spin lock and hold HW spinlock */
spin_unlock_irqrestore(&((smem_spinlock)->local), flags);
queue_work(ws, &work_item.work);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&work_item.try_lock, HZ * 2), >, 0);
UT_ASSERT_INT((int)work_item.has_locked, ==, 0);
spinlock_owner = remote_spin_owner(smem_spinlock);
UT_ASSERT_INT(spinlock_owner, ==, SMEM_APPS);
remote_spin_release_all(SMEM_APPS);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&work_item.locked, HZ * 2), >, 0);
if (!failed)
seq_puts(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_puts(s, "\tFailed\n");
}
}
/**
* smp2p_ut_remote_spinlock_track_core - Verify remote spinlock.
*
* @s: Pointer to output file
* @remote_pid: Remote processor to test
*
* This test has the remote subsystem grab the lock, and then has the local
* subsystem attempt to grab the lock using the trylock() API. It then verifies
* that the ID in the hw_spinlocks array matches the owner of the lock.
*/
static void smp2p_ut_remote_spinlock_track_core(struct seq_file *s,
int remote_pid)
{
int failed = 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;
int stored_value;
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 */
flags = 0;
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
/* try to acquire spinlock */
remote_spin_trylock_irqsave(smem_spinlock, flags);
/*
* Need to check against the locking token (PID + 1)
* because the remote_spin_owner() API only returns the
* PID.
*/
stored_value = remote_spin_get_hw_spinlocks_element(
smem_spinlock);
UT_ASSERT_INT(stored_value, ==,
remote_spin_owner(smem_spinlock) + 1);
UT_ASSERT_INT(stored_value, ==, remote_pid + 1);
/* End test */
test_request = 0x0;
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
SMP2P_SET_RMT_DATA(test_request, 0x0);
(void)msm_smp2p_out_write(handle, test_request);
/* 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)
seq_puts(s, "\tOK\n");
} while (0);
if (failed) {
if (handle) {
/* send end command */
test_request = 0x0;
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
SMP2P_SET_RMT_DATA(test_request, 0x0);
(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_puts(s, "\tFailed\n");
}
}
/**
* smp2p_ut_remote_spinlock_track - Verify PID tracking for modem.
*
* @s: Pointer to output file
* @pid: The processor to test
*/
static void smp2p_ut_remote_spinlock_track(struct seq_file *s, int pid)
{
struct smp2p_interrupt_config *int_cfg;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg) {
seq_puts(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_track_core(s, pid);
msm_smp2p_init_rmt_lpb_proc(pid);
}
/**
* smp2p_ut_remote_spinlock_track - Verify PID tracking for all processors.
*
* @s: Pointer to output file
*
* This test verifies PID tracking for all configured remote processors.
*/
static void smp2p_ut_remote_spinlock_track_modem(struct seq_file *s)
{
smp2p_ut_remote_spinlock_track(s, SMP2P_MODEM_PROC);
}
static void smp2p_ut_remote_spinlock_track_adsp(struct seq_file *s)
{
smp2p_ut_remote_spinlock_track(s, SMP2P_AUDIO_PROC);
}
static void smp2p_ut_remote_spinlock_track_dsps(struct seq_file *s)
{
smp2p_ut_remote_spinlock_track(s, SMP2P_SENSOR_PROC);
}
static void smp2p_ut_remote_spinlock_track_wcnss(struct seq_file *s)
{
smp2p_ut_remote_spinlock_track(s, SMP2P_WIRELESS_PROC);
}
static void smp2p_ut_remote_spinlock_track_tz(struct seq_file *s)
{
smp2p_ut_remote_spinlock_track(s, SMP2P_TZ_PROC);
}
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_dsps",
smp2p_ut_remote_spinlock_dsps);
smp2p_debug_create("ut_remote_spinlock_wcnss",
smp2p_ut_remote_spinlock_wcnss);
smp2p_debug_create("ut_remote_spinlock_tz",
smp2p_ut_remote_spinlock_tz);
smp2p_debug_create("ut_remote_spinlock_rpm",
smp2p_ut_remote_spinlock_rpm);
smp2p_debug_create_u32("ut_remote_spinlock_time",
&ut_remote_spinlock_run_time);
smp2p_debug_create("ut_remote_spinlock_ssr",
&smp2p_ut_remote_spinlock_ssr);
smp2p_debug_create("ut_remote_spinlock_track_modem",
&smp2p_ut_remote_spinlock_track_modem);
smp2p_debug_create("ut_remote_spinlock_track_adsp",
&smp2p_ut_remote_spinlock_track_adsp);
smp2p_debug_create("ut_remote_spinlock_track_dsps",
&smp2p_ut_remote_spinlock_track_dsps);
smp2p_debug_create("ut_remote_spinlock_track_wcnss",
&smp2p_ut_remote_spinlock_track_wcnss);
smp2p_debug_create("ut_remote_spinlock_track_tz",
&smp2p_ut_remote_spinlock_track_tz);
return 0;
}
module_init(smp2p_debugfs_init);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,213 @@
/* drivers/soc/qcom/smp2p_test_common.h
*
* Copyright (c) 2013-2014, 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 _SMP2P_TEST_COMMON_H_
#define _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 *));
void smp2p_debug_create_u32(const char *name, uint32_t *value);
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)
{
reinit_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 /* _SMP2P_TEST_COMMON_H_ */