msm: smp2p: Add new SMSM Point-to-Point Driver

The Shared Memory Point to Point (SMP2P) driver facilitates
communication of multiple 32-bit values between two processors.  Each
value (called a state entry) has a single writer (the local side) and a
single reader (the remote side).  Entries are uniquely identified in the
system by the directed edge (local processor ID to remote processor ID)
and a string identifier.

This driver, which is layered on top of shared memory (SMEM), will
eventually replace the existing broadcast implementation of SMSM.  This
driver has several advantages over SMSM including:
 * Point-to-point communication which allows for XPU protection of SMEM
 * Multiple 32-bit state values for future expansion
 * GPIO and Virtual interrupt support for client-facing API
 * Version and feature negotiation to allow for phased upgrades of
   a system without requiring cross-processor dependencies.

Change-Id: Iefe2169ae686a3e5f400b3a7039f6b114a8b7e5a
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
This commit is contained in:
Eric Holmberg 2012-11-19 13:05:04 -07:00 committed by Stephen Boyd
parent 792883d779
commit 4b9d5de206
18 changed files with 6337 additions and 5 deletions

View File

@ -0,0 +1,416 @@
Introduction
============
The Shared Memory Point to Point (SMP2P) protocol facilitates communication of
a single 32-bit value between two processors. Each value has a single writer
(the local side) and a single reader (the remote side). Values are uniquely
identified in the system by the directed edge (local processor ID to remote
processor ID) and a string identifier.
Version and feature negotiation has been included in the design to allow for
phased upgrades of all processors.
Software Architecture Description
=================================
The data and interrupt coupling between processors is shown in Fig. 1. Each
processor is responsible for creating the outgoing SMEM items and each item is
writable by the local processor and readable by the remote processor. By using
two separate SMEM items that are single-reader and single-writer, SMP2P does
not require any remote locking mechanisms.
The client API uses the Linux GPIO and interrupt framework to expose a virtual
GPIO and a virtual interrupt controller for each entry.
=================
| |
-----write------>|SMEM item A->B |-----read------
| | | |
| ================= |
| |
| v
GPIO API => ------------ ======= Interrupt line ======> ------------
Processor A Processor B
Interrupt <= ------------ <====== Interrupt line ======= ------------
API ^ |
| |
| |
| ================= |
| | | |
------read-------|SMEM item A<-B |<-----write----
| |
=================
Fig 1
Design
======
Each SMEM item contains a header that is used to identify and manage the edge
along with an array of actual entries. The overall structure is captured in
Fig 2 and the details of the header and entries are covered later in this
section. The memory format of all shared structures is little-endian.
-----------------------------------------------
| SMEM item A->B |
| |
| ----------------------------------------- |
| |31 24| 16| 8| 0| |
| |----------|---------|----------|---------| |
| | Identifier Constant(Magic Number) | |
| |----------|---------|----------|---------| |
| | Feature Flags |Version | |
| | |Number | |
| |----------|---------|----------|---------| |
| | Remote Proc ID |Local Proc ID | |
| |----------|---------|----------|---------| |
| | Entries Valid | Entries Total | |
| |-----------------------------------------| |
| |
| |
| ----------------------------------------- |
| | Entry 0 | |
| | ---------------------------------- | |
| | | Identifier String | | |
| | |---------------------------------| | |
| | | Data | | |
| | |---------------------------------| | |
| |---------------------------------------| |
| ----------------------------------------- |
| | Entry 1 | |
| | ---------------------------------- | |
| | | Identifier String | | |
| | |---------------------------------| | |
| | | Data | | |
| | |---------------------------------| | |
| |---------------------------------------| |
| - |
| - |
| - |
| ----------------------------------------- |
| | Entry N | |
| | ---------------------------------- | |
| | | Identifier String | | |
| | |---------------------------------| | |
| | | Data | | |
| | |---------------------------------| | |
| |---------------------------------------| |
-----------------------------------------------
Fig 2
The header of each SMEM item contains metadata that describes the processors
using the edge, the version information, and the entry count. The constant
identifier is used as a magic number to enable extraction of the items from a
memory dump. The size of each entry depends upon the version, but the number
of total entries (and hence the size of each SMEM item) is configurable with a
suggested value of 16.
The number of valid entries is used to indicate how many of the Entries Total
are currently used and are current valid.
---------------------------------------------------------------------------
|Field Size Description Valid Values |
---------------------------------------------------------------------------
| Identifier 4 Bytes Value used to identify |
| Constant structure in memory. Must be set to $SMP |
| Useful for debugging. (0x504D5324) |
---------------------------------------------------------------------------
| Local 2 Bytes Writing processor ID. Refer Processor ID Table 3|
| Processor |
| ID |
---------------------------------------------------------------------------
| Remote 2 Bytes Reading processor ID. Refer Processor ID Table 3|
| Processor |
| ID |
---------------------------------------------------------------------------
| Version 1 Bytes Refer to Version |
| Number Feature Negotiation Must be set to 1. |
| section. |
---------------------------------------------------------------------------
| Feature 3 Bytes Refer to Version |
| flags and Feature Negotiation Must be set to zero. |
| section. |
---------------------------------------------------------------------------
| Entries 2 Bytes Total number of Must be 0 or greater. |
| Total entries. |
---------------------------------------------------------------------------
| Entries 2 Bytes Number of valid Must be between 0 |
| Valid entries. and Entries Total. |
---------------------------------------------------------------------------
| Reserved 4 Bytes Reserved Must be set to 0. |
---------------------------------------------------------------------------
Table 1 - SMEM Item Header
The content of each SMEM entries is described in Table 2 and consists of a
string identifier and a 32-bit data value. The string identifier must be
unique for each SMEM item. The data value is opaque to SMP2P giving the client
complete flexibility as to its usage.
----------------------- --------------------- -----------------------------
| Field | Size | Description | Valid Values |
------------|----------|---------------------|-----------------------------
| | | | |
| Identifier | 16 Bytes | Null Terminated | NON-NULL for |
| String | | ASCII string. | valid entries. |
| | | | |
------------|----------|---------------------|-----------------------------
| Data | 4 Bytes | Data | Any (client defined) |
------------ ---------- --------------------- -----------------------------
Table 2 - Entry Format
The processor IDs in the system are fixed and new processors IDs will be
added to the end of the list (Table 3).
-------------------------------------------------
| Processor Name | ID value |
-------------------------------------------------
| Application processor | 0 |
-------------------------------------------------
| Modem processor | 1 |
-------------------------------------------------
| Audio processor | 2 |
-------------------------------------------------
| Sensor processor | 3 |
-------------------------------------------------
| Wireless processor | 4 |
-------------------------------------------------
| Modem Fw | 5 |
-------------------------------------------------
| Power processor | 6 |
-------------------------------------------------
| NUM PROCESSORS | 7 |
-------------------------------------------------
Table 3 - Processor IDs
SMEM Item
---------
The responsibility of creating an SMEM item is with the local processor that is
initiating outbound traffic. After creating the item, the local and remote
processors negotiate the version and feature flags for the item to ensure
compatibility.
Table 4 lists the SMEM item base identifiers. To get the SMEM item ID for a
particular edge, the remote processor ID (Table 3) is added to the base item ID
for the local processor (Table 4). For example, the Apps ==> Modem (id 1) SMEM
Item ID will be 427 + 1 = 428.
--------------------------------------------------
| Description | SMEM ID value |
--------------------------------------------------
| Apps SMP2P SMEM Item base | 427 |
--------------------------------------------------
| Modem SMP2P SMEM Item base | 435 |
--------------------------------------------------
| Audio SMP2P SMEM Item base | 443 |
--------------------------------------------------
| Wireless SMP2P SMEM Item base | 451 |
--------------------------------------------------
| Power SMP2P SMEM Item base | 459 |
--------------------------------------------------
Table 4 - SMEM Items Base IDs
Version and Feature Negotiation
-------------------------------
To enable upgrading without breaking the system and to enable graceful feature
fall-back support, SMP2P supports a version number and feature flags. The
combination of the version number and feature flags enable:
1) SMP2P software updates to be rolled out to each processor separately.
2) Individual features to be enabled or disabled per connection or edge.
The version number represents any change in SMP2P that breaks compatibility
between processors. Examples would be a change in the shared data structures
or changes to fundamental behavior. Each implementation of SMP2P must be able
to support a minimum of the current version and the previous version.
The feature flags represent any changes in SMP2P that are optional and
backwards compatible. Endpoints will negotiate the supported flag when the
SMEM items are created and they cannot be changed after negotiation has been
completed.
Negotiation Algorithm
----------------------
While creating the SMEM item the following algorithm shall be used.
if remote endpoint's SMEM Item exists
Read remote version number and flags
Local version number must be lower of
- remote version number
- highest supported local version number
Flags value is bitwise AND of
- remote feature flags
- locally supported flags
Create SMEM item and populate negotiated number and flags
Interrupt remote processor
if version and flags match, negotiation is complete, else wait
for remote interrupt below.
Else
Create SMEM item and populate it with highest supported version and any
requested feature flag.
Interrupt remote processor.
Wait for Interrupt below.
Upon receiving the interrupt from remote processor and negotiation is not
complete, check the version number and feature flags:
if equal, negotiation is complete.
if remote number is less than local number, and remote number is
supported:
Set local version number to remote version number
Bitwise AND local flags with remote flags
Interrupt remote processor
Negotiation is complete
if remote number is not supported, then negotiation has failed
Set version number to 0xFF and report failure in kernel log.
if remote number is more than local number:
Wait for remote endpoint to process our interrupt and negotiate down.
Creating an SMEM Entry
----------------------
Each new SMEM entry used in data transfer must be created at the end of the
entry array in the SMEM item and cannot be deleted until the system is
rebooted. The following sequence is be followed:
1) Compare Entries Valid and Entries Total to verify if there is room in the
entry array for this request (if not, return error code to client).
2) Populate the Identifier of new entry and do a write memory barrier.
3) Update Entries Valid and Entries Total and do a write memory barrier.
4) Interrupt remote endpoint.
Entry Write
-----------
An entry write is achieved by the following sequence of operations:
1) Update data field in the entry and do a write memory barrier.
2) Interrupt remote endpoint.
Entry Read / Receiving Interrupts
---------------------------------
An interrupt will be received from the remote system for one or more of the following events:
1) Initialization
2) Entry change
3) New Entry
As long as the SMEM item initialization is complete, then each interrupt should
trigger SMP2P to:
1) Compare valid entry data value to cached value and notify client if it
has changed.
2) Compare Entries Valid to cached value. If changed, initialize new entries.
Performance
===========
No performance issues are anticipated as the signaling rate is expected to be
low and is performed in interrupt context which minimizes latency.
Interfaces
================
SMP2P is only supported in the kernel and interfaces with clients through the
GPIO and interrupt subsystems.
To map an entry to the client, the client must add two nodes to the Device
Tree:
1) A node that matches "qcom,smp2pgpio" to create the entry
2) A node that matches the client driver to provide the GPIO pin mapping
The details of the device tree entries are contained in the file
Documentionat/devicetree/bindings/arm/msm/smp2p.txt.
/* SMP2P Test Driver for inbound entry. */
smp2pgpio_smp2p_7_in: qcom,smp2pgpio-smp2p-7-in {
compatible = "qcom,smp2pgpio";
qcom,entry-name = "smp2p";
qcom,remote-pid = <7>;
qcom,is_inbound;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/* SMP2P Test Client for inbound entry. */
qcom,smp2pgpio_test_smp2p_7_in {
compatible = "qcom,smp2pgpio_test_smp2p_7_in";
gpios = <&smp2pgpio_smp2p_7_in 0 0>,
<&smp2pgpio_smp2p_7_in 1 0>,
. . .
<&smp2pgpio_smp2p_7_in 31 0>;
};
/* SMP2P Test Driver for outbound entries */
smp2pgpio_smp2p_345_out: qcom,smp2pgpio-smp2p-7-out {
compatible = "qcom,smp2pgpio";
qcom,entry-name = "smp2p";
qcom,remote-pid = <7>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/* SMP2P Test Client for outbound entry. */
qcom,smp2pgpio_test_smp2p_7_out {
compatible = "qcom,smp2pgpio_test_smp2p_7_out";
gpios = <&smp2pgpio_smp2p_7_out 0 0>,
<&smp2pgpio_smp2p_7_out 1 0>,
. . .
<&smp2pgpio_smp2p_7_out 31 0>;
The client can use a match entry for "qcom,smp2pgpio_test_smp2p_7_in" to
retrieve the Device Tree configuration node. Once that node has been
retrieved, the client can call of_get_gpio() to get the virtual GPIO pin and
also use gpio_to_irq() to map the GPIO pin to a virtual interrupt. After that
point, the standard GPIO and Interrupt APIs can be used to manipulate the SMP2P
entries and receive notifications of changes. Examples of typical function
calls are shown below:
of_get_gpio()
gpio_get_value()
gpio_set_value()
gpio_to_irq()
request_irq()
free_irq()
Please reference the unit test code for example usage.
Debug
=====
The state values and names for all entries accessible by the Apps are
accessible through debugfs nodes for general debug purposes.
Debugfs entries for triggering unit-tests are also exported.
Internal logging will be performed using the IPC Logging module to enable
post-mortem analysis.
Testing
=======
On-target unit testing will be done to verify internal functionality and the
GPIO/IRQ API's.
Driver parameters
=================
One module parameter will be provided to change the verbosity of internal logging.
Config options
==============
Configuration of interrupts will be done using Device Tree. By default, the
testing components will be enabled since it does not affect performance and has
a minimal impact on kernel size. However, customers can disable the testing
components for size optimization.
CONFIG_MSM_SMP2P - enables SMP2P core functionality
CONFIG_MSM_SMP2P_TEST - enables unit test support
Dependencies
===========
Requires SMEM for creating the SMEM items.
User Space utilities
====================
No userspace utilities are planned.
Known issues
============
None.

View File

@ -0,0 +1,21 @@
Qualcomm SMSM Point-to-Point (SMP2P)
Required properties:
-compatible : should be "qcom,smp2p"
-reg : the location and offset of the irq register base memory
-reg-names : "irq-reg-base", "irq-reg-offset" - string to identify the irq
register region and offset values
-qcom,remote-pid : the SMP2P remote processor ID (see smp2p_private_api.h)
-qcom,irq-bitmask : the sending irq bitmask
-interrupts : the receiving interrupt line
Example:
qcom,smp2p-modem {
compatible = "qcom,smp2p";
reg = <0xfa006000 0x1000>, <0x8 0x0>;
reg-names = "irq-reg-base", "irq-reg-offset";
qcom,remote-pid = <1>;
qcom,irq-bitmask = <0x4000>;
interrupts = <0 27 1>;
};

View File

@ -0,0 +1,77 @@
Qualcomm SMSM Point-to-Point (SMP2P) GPIO Driver
Used to map an SMP2P entry and remote processor ID to a virtual GPIO controller
and virtual interrupt controller.
Required properties:
-compatible : should be "qcom,smp2pgpio";
-qcom,entry-name : name of the SMP2P entry
-qcom,remote-pid : the SMP2P remote processor ID (see smp2p_private_api.h)
-gpio-controller : specifies that this is a GPIO controller
-#gpio-cells : number of GPIO cells (should always be <2>)
-interrupt-controller : specifies that this is an interrupt controller
-#interrupt-cells : number of interrupt cells (should always be <2>)
Optional properties:
-qcom,is-inbound : specifies that this is an inbound entry (default is outbound)
Comments:
All device tree entries must be unique. Therefore to prevent naming collisions
between clients, it is recommended that the DT nodes should be named using the
format:
smp2pgpio_<ENTRY_NAME>_<REMOTE PID>_<in|out>
Example:
/* Maps inbound "smp2p" entry on remote PID 7 to GPIO controller. */
smp2pgpio_smp2p_7_in: qcom,smp2pgpio-smp2p-7-in {
compatible = "qcom,smp2pgpio";
qcom,entry-name = "smp2p";
qcom,remote-pid = <7>;
qcom,is-inbound;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/*
* Maps inbound "smp2p" entry on remote PID 7 to client driver
* "qcom,smp2pgpio_test_smp2p_7_in".
*
* Note: If all 32-pins are used by this client, then you
* can just list pin 0 here as a shortcut.
*/
qcom,smp2pgpio_test_smp2p_7_in {
compatible = "qcom,smp2pgpio_test_smp2p_7_in";
gpios = <&smp2pgpio_smp2p_7_in 0 0>, /* pin 0 */
<&smp2pgpio_smp2p_7_in 1 0>,
. . .
<&smp2pgpio_smp2p_7_in 31 0>; /* pin 31 */
};
/* Maps outbound "smp2p" entry on remote PID 7 to GPIO controller. */
smp2pgpio_smp2p_7_out: qcom,smp2pgpio-smp2p-7-out {
compatible = "qcom,smp2pgpio";
qcom,entry-name = "smp2p";
qcom,remote-pid = <7>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/*
* Maps outbound "smp2p" entry on remote PID 7 to client driver
* "qcom,smp2pgpio_test_smp2p_7_out".
*
* Note: If all 32-pins are used by this client, then you
* can just list pin 0 here as a shortcut.
*/
qcom,smp2pgpio_test_smp2p_7_out {
compatible = "qcom,smp2pgpio_test_smp2p_7_out";
gpios = <&smp2pgpio_smp2p_7_out 0 0>, /* pin 0 */
<&smp2pgpio_smp2p_7_out 1 0>,
. . .
<&smp2pgpio_smp2p_7_out 31 0>; /* pin 31 */
};

View File

@ -1447,6 +1447,27 @@ config MSM_N_WAY_SMSM
Supports APPS-QDSP SMSM communication along with
normal APPS-MODEM SMSM communication.
config MSM_SMP2P
bool "SMSM Point-to-Point (SMP2P)"
depends on MSM_SMD
help
Provide point-to-point remote signaling support.
SMP2P enables transferring 32-bit values between
the local and a remote system using shared
memory and interrupts. A client can open multiple
32-bit values by specifying a unique string and
remote processor ID.
config MSM_SMP2P_TEST
bool "SMSM Point-to-Point Test"
depends on MSM_SMP2P
help
Enables loopback and unit testing support for
SMP2P. Loopback support is used by other
processors to do unit testing. Unit tests
are used to verify the local and remote
implementations.
config MSM_RESET_MODEM
tristate "Reset Modem Driver"
depends on MSM_SMD

View File

@ -69,7 +69,8 @@ $(obj)/smd_rpc_sym.c: $(src)/smd_rpc_sym $(src)/mkrpcsym.pl
$(call if_changed,mkrpcsym)
obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o remote_spinlock.o smd_private.o
obj-$(CONFIG_MSM_SMP2P) += smp2p.o smp2p_debug.o smp2p_gpio.o
obj-$(CONFIG_MSM_SMP2P_TEST) += smp2p_loopback.o smp2p_test.o smp2p_gpio_test.o smp2p_spinlock_test.o
obj-$(CONFIG_MSM_SCM) += scm.o scm-boot.o
obj-$(CONFIG_MSM_SECURE_IO) += scm-io.o
obj-$(CONFIG_MSM_PIL) += peripheral-loader.o

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -198,7 +198,11 @@ enum {
SMEM_SSR_REASON_LPASS0,
SMEM_SSR_REASON_DSPS0,
SMEM_SSR_REASON_VCODEC0,
SMEM_MEM_LAST = SMEM_SSR_REASON_VCODEC0,
SMEM_SMP2P_APPS_BASE = 427,
SMEM_SMP2P_MODEM_BASE = 435,
SMEM_SMP2P_AUDIO_BASE = 443,
SMEM_SMP2P_WIRLESS_BASE = 451,
SMEM_SMP2P_POWER_BASE = 459,
SMEM_NUM_ITEMS,
};

View File

@ -1,7 +1,7 @@
/* arch/arm/mach-msm/smd.c
*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2008-2012, The Linux Foundation. All rights reserved.
* Copyright (c) 2008-2013, The Linux Foundation. All rights reserved.
* Author: Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
@ -3152,6 +3152,17 @@ int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask,
}
EXPORT_SYMBOL(smsm_state_cb_deregister);
/**
* smem_get_remote_spinlock - Remote spinlock pointer for unit testing.
*
* @returns: pointer to SMEM remote spinlock
*/
remote_spinlock_t *smem_get_remote_spinlock(void)
{
return &remote_spinlock;
}
EXPORT_SYMBOL(smem_get_remote_spinlock);
int smd_module_init_notifier_register(struct notifier_block *nb)
{
int ret;

View File

@ -1,7 +1,7 @@
/* arch/arm/mach-msm/smd_private.h
*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2007-2012, The Linux Foundation. All rights reserved.
* Copyright (c) 2007-2013, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@ -18,6 +18,8 @@
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/remote_spinlock.h>
#include <mach/msm_smsm.h>
#include <mach/msm_smd.h>
@ -271,4 +273,6 @@ struct interrupt_stat {
};
extern struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS];
/* used for unit testing spinlocks */
remote_spinlock_t *smem_get_remote_spinlock(void);
#endif

1706
arch/arm/mach-msm/smp2p.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,328 @@
/* arch/arm/mach-msm/smp2p_debug.c
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/ctype.h>
#include <linux/list.h>
#include <linux/debugfs.h>
#include "smp2p_private.h"
#if defined(CONFIG_DEBUG_FS)
/**
* Dump interrupt statistics.
*
* @s: pointer to output file
*/
static void smp2p_int_stats(struct seq_file *s)
{
struct smp2p_interrupt_config *int_cfg;
int pid;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg)
return;
seq_printf(s, "| Processor | Incoming Id | Incoming # |");
seq_printf(s, " Outgoing # | Base Ptr | Mask |\n");
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
if (!int_cfg[pid].is_configured &&
pid != SMP2P_REMOTE_MOCK_PROC)
continue;
seq_printf(s,
"| %5s (%d) | %11u | %10u | %10u | %p | %08x |\n",
int_cfg[pid].name,
pid, int_cfg[pid].in_int_id,
int_cfg[pid].in_interrupt_count,
int_cfg[pid].out_interrupt_count,
int_cfg[pid].out_int_ptr,
int_cfg[pid].out_int_mask);
}
}
/**
* Dump item header line 1.
*
* @buf: output buffer
* @max: length of output buffer
* @item_ptr: SMEM item pointer
* @state: item state
* @returns: Number of bytes written to output buffer
*/
static int smp2p_item_header1(char *buf, int max, struct smp2p_smem *item_ptr,
enum msm_smp2p_edge_state state)
{
int i = 0;
const char *state_text;
if (!item_ptr) {
i += scnprintf(buf + i, max - i, "None");
return i;
}
switch (state) {
case SMP2P_EDGE_STATE_CLOSED:
state_text = "State: Closed";
break;
case SMP2P_EDGE_STATE_OPENING:
state_text = "State: Opening";
break;
case SMP2P_EDGE_STATE_OPENED:
state_text = "State: Opened";
break;
default:
state_text = "";
break;
}
i += scnprintf(buf + i, max - i,
"%-14s LPID %d RPID %d",
state_text,
SMP2P_GET_LOCAL_PID(item_ptr->rem_loc_proc_id),
SMP2P_GET_REMOTE_PID(item_ptr->rem_loc_proc_id)
);
return i;
}
/**
* Dump item header line 2.
*
* @buf: output buffer
* @max: length of output buffer
* @item_ptr: SMEM item pointer
* @returns: Number of bytes written to output buffer
*/
static int smp2p_item_header2(char *buf, int max, struct smp2p_smem *item_ptr)
{
int i = 0;
if (!item_ptr) {
i += scnprintf(buf + i, max - i, "None");
return i;
}
i += scnprintf(buf + i, max - i,
"Version: %08x Features: %08x",
SMP2P_GET_VERSION(item_ptr->feature_version),
SMP2P_GET_FEATURES(item_ptr->feature_version)
);
return i;
}
/**
* Dump item header line 3.
*
* @buf: output buffer
* @max: length of output buffer
* @item_ptr: SMEM item pointer
* @state: item state
* @returns: Number of bytes written to output buffer
*/
static int smp2p_item_header3(char *buf, int max, struct smp2p_smem *item_ptr)
{
int i = 0;
if (!item_ptr) {
i += scnprintf(buf + i, max - i, "None");
return i;
}
i += scnprintf(buf + i, max - i,
"Entries Valid/Max: %d/%d",
SMP2P_GET_ENT_VALID(item_ptr->valid_total_ent),
SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent)
);
return i;
}
/**
* Dump individual input/output item pair.
*
* @s: pointer to output file
*/
static void smp2p_item(struct seq_file *s, int remote_pid)
{
struct smp2p_smem *out_ptr;
struct smp2p_smem *in_ptr;
struct smp2p_interrupt_config *int_cfg;
char tmp_buff[64];
int state;
int entry;
struct smp2p_entry_v1 *out_entries = NULL;
struct smp2p_entry_v1 *in_entries = NULL;
int out_valid = 0;
int in_valid = 0;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg)
return;
if (!int_cfg[remote_pid].is_configured &&
remote_pid != SMP2P_REMOTE_MOCK_PROC)
return;
out_ptr = smp2p_get_out_item(remote_pid, &state);
in_ptr = smp2p_get_in_item(remote_pid);
if (!out_ptr && !in_ptr)
return;
/* print item headers */
seq_printf(s, "%s%s\n",
" ====================================== ",
"======================================");
scnprintf(tmp_buff, sizeof(tmp_buff),
"Apps(%d)->%s(%d)",
SMP2P_APPS_PROC, int_cfg[remote_pid].name, remote_pid);
seq_printf(s, "| %-37s", tmp_buff);
scnprintf(tmp_buff, sizeof(tmp_buff),
"%s(%d)->Apps(%d)",
int_cfg[remote_pid].name, remote_pid, SMP2P_APPS_PROC);
seq_printf(s, "| %-37s|\n", tmp_buff);
seq_printf(s, "%s%s\n",
" ====================================== ",
"======================================");
smp2p_item_header1(tmp_buff, sizeof(tmp_buff), out_ptr, state);
seq_printf(s, "| %-37s", tmp_buff);
smp2p_item_header1(tmp_buff, sizeof(tmp_buff), in_ptr, -1);
seq_printf(s, "| %-37s|\n", tmp_buff);
smp2p_item_header2(tmp_buff, sizeof(tmp_buff), out_ptr);
seq_printf(s, "| %-37s", tmp_buff);
smp2p_item_header2(tmp_buff, sizeof(tmp_buff), in_ptr);
seq_printf(s, "| %-37s|\n", tmp_buff);
smp2p_item_header3(tmp_buff, sizeof(tmp_buff), out_ptr);
seq_printf(s, "| %-37s", tmp_buff);
smp2p_item_header3(tmp_buff, sizeof(tmp_buff), in_ptr);
seq_printf(s, "| %-37s|\n", tmp_buff);
seq_printf(s, " %s%s\n",
"-------------------------------------- ",
"--------------------------------------");
seq_printf(s, "| %-37s",
"Entry Name Value");
seq_printf(s, "| %-37s|\n",
"Entry Name Value");
seq_printf(s, " %s%s\n",
"-------------------------------------- ",
"--------------------------------------");
/* print entries */
if (out_ptr) {
out_entries = (struct smp2p_entry_v1 *)((void *)out_ptr +
sizeof(struct smp2p_smem));
out_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent);
}
if (in_ptr) {
in_entries = (struct smp2p_entry_v1 *)((void *)in_ptr +
sizeof(struct smp2p_smem));
in_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent);
}
for (entry = 0; out_entries || in_entries; ++entry) {
if (out_entries && entry < out_valid) {
scnprintf(tmp_buff, sizeof(tmp_buff),
"%-16s 0x%08x",
out_entries->name,
out_entries->entry);
++out_entries;
} else {
out_entries = NULL;
scnprintf(tmp_buff, sizeof(tmp_buff), "None");
}
seq_printf(s, "| %-37s", tmp_buff);
if (in_entries && entry < in_valid) {
scnprintf(tmp_buff, sizeof(tmp_buff),
"%-16s 0x%08x",
in_entries->name,
in_entries->entry);
++in_entries;
} else {
in_entries = NULL;
scnprintf(tmp_buff, sizeof(tmp_buff), "None");
}
seq_printf(s, "| %-37s|\n", tmp_buff);
}
seq_printf(s, " %s%s\n\n",
"-------------------------------------- ",
"--------------------------------------");
}
/**
* Dump item state.
*
* @s: pointer to output file
*/
static void smp2p_items(struct seq_file *s)
{
int pid;
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
smp2p_item(s, pid);
}
static struct dentry *dent;
static int debugfs_show(struct seq_file *s, void *data)
{
void (*show)(struct seq_file *) = s->private;
show(s);
return 0;
}
static int debug_open(struct inode *inode, struct file *file)
{
return single_open(file, debugfs_show, inode->i_private);
}
static const struct file_operations debug_ops = {
.open = debug_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
};
void debug_create(const char *name,
void (*show)(struct seq_file *))
{
struct dentry *file;
file = debugfs_create_file(name, 0444, dent, show, &debug_ops);
if (!file)
pr_err("%s: unable to create file '%s'\n", __func__, name);
}
static int __init smp2p_debugfs_init(void)
{
dent = debugfs_create_dir("smp2p", 0);
if (IS_ERR(dent))
return PTR_ERR(dent);
debug_create("int_stats", smp2p_int_stats);
debug_create("items", smp2p_items);
return 0;
}
late_initcall(smp2p_debugfs_init);
#endif /* CONFIG_DEBUG_FS */

View File

@ -0,0 +1,757 @@
/* arch/arm/mach-msm/smp2p_gpio.c
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/bitmap.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <mach/msm_ipc_logging.h>
#include "smp2p_private_api.h"
#include "smp2p_private.h"
/* GPIO device - one per SMP2P entry. */
struct smp2p_chip_dev {
struct list_head entry_list;
char name[SMP2P_MAX_ENTRY_NAME];
int remote_pid;
bool is_inbound;
bool is_open;
struct notifier_block out_notifier;
struct notifier_block in_notifier;
struct msm_smp2p_out *out_handle;
struct gpio_chip gpio;
struct irq_domain *irq_domain;
int irq_base;
spinlock_t irq_lock;
DECLARE_BITMAP(irq_enabled, SMP2P_BITS_PER_ENTRY);
DECLARE_BITMAP(irq_rising_edge, SMP2P_BITS_PER_ENTRY);
DECLARE_BITMAP(irq_falling_edge, SMP2P_BITS_PER_ENTRY);
};
static struct platform_driver smp2p_gpio_driver;
static struct lock_class_key smp2p_gpio_lock_class;
static struct irq_chip smp2p_gpio_irq_chip;
static DEFINE_SPINLOCK(smp2p_entry_lock_lha1);
static LIST_HEAD(smp2p_entry_list);
/* Used for mapping edge to name for logging. */
static const char * const edge_names[] = {
"-",
"0->1",
"1->0",
"-",
};
/* Used for mapping edge to value for logging. */
static const char * const edge_name_rising[] = {
"-",
"0->1",
};
/* Used for mapping edge to value for logging. */
static const char * const edge_name_falling[] = {
"-",
"1->0",
};
static int smp2p_gpio_to_irq(struct gpio_chip *cp,
unsigned offset);
/**
* smp2p_get_value - Retrieves GPIO value.
*
* @cp: GPIO chip pointer
* @offset: Pin offset
* @returns: >=0: value of GPIO Pin; < 0 for error
*
* Error codes:
* -ENODEV - chip/entry invalid
* -ENETDOWN - valid entry, but entry not yet created
*/
static int smp2p_get_value(struct gpio_chip *cp,
unsigned offset)
{
struct smp2p_chip_dev *chip;
int ret = 0;
uint32_t data;
if (!cp)
return -ENODEV;
chip = container_of(cp, struct smp2p_chip_dev, gpio);
if (!chip->is_open)
return -ENETDOWN;
if (chip->is_inbound)
ret = msm_smp2p_in_read(chip->remote_pid, chip->name, &data);
else
ret = msm_smp2p_out_read(chip->out_handle, &data);
if (!ret)
ret = (data & (1 << offset)) ? 1 : 0;
return ret;
}
/**
* smp2p_set_value - Sets GPIO value.
*
* @cp: GPIO chip pointer
* @offset: Pin offset
* @value: New value
*/
static void smp2p_set_value(struct gpio_chip *cp, unsigned offset, int value)
{
struct smp2p_chip_dev *chip;
uint32_t data_set;
uint32_t data_clear;
int ret;
if (!cp)
return;
chip = container_of(cp, struct smp2p_chip_dev, gpio);
if (!chip->is_open)
return;
if (chip->is_inbound) {
SMP2P_ERR("%s: '%s':%d virq %d invalid operation\n",
__func__, chip->name, chip->remote_pid,
chip->irq_base + offset);
return;
}
if (value) {
data_set = 1 << offset;
data_clear = 0;
} else {
data_set = 0;
data_clear = 1 << offset;
}
ret = msm_smp2p_out_modify(chip->out_handle,
data_set, data_clear);
if (ret)
SMP2P_GPIO("'%s':%d gpio %d set to %d failed (%d)\n",
chip->name, chip->remote_pid,
chip->gpio.base + offset, value, ret);
else
SMP2P_GPIO("'%s':%d gpio %d set to %d\n",
chip->name, chip->remote_pid,
chip->gpio.base + offset, value);
}
/**
* smp2p_direction_input - Sets GPIO direction to input.
*
* @cp: GPIO chip pointer
* @offset: Pin offset
* @returns: 0 for success; < 0 for failure
*/
static int smp2p_direction_input(struct gpio_chip *cp, unsigned offset)
{
struct smp2p_chip_dev *chip;
if (!cp)
return -ENODEV;
chip = container_of(cp, struct smp2p_chip_dev, gpio);
if (!chip->is_inbound)
return -EPERM;
return 0;
}
/**
* smp2p_direction_output - Sets GPIO direction to output.
*
* @cp: GPIO chip pointer
* @offset: Pin offset
* @value: Direction
* @returns: 0 for success; < 0 for failure
*/
static int smp2p_direction_output(struct gpio_chip *cp,
unsigned offset, int value)
{
struct smp2p_chip_dev *chip;
if (!cp)
return -ENODEV;
chip = container_of(cp, struct smp2p_chip_dev, gpio);
if (chip->is_inbound)
return -EPERM;
return 0;
}
/**
* smp2p_gpio_to_irq - Convert GPIO pin to virtual IRQ pin.
*
* @cp: GPIO chip pointer
* @offset: Pin offset
* @returns: >0 for virtual irq value; < 0 for failure
*/
static int smp2p_gpio_to_irq(struct gpio_chip *cp, unsigned offset)
{
struct smp2p_chip_dev *chip;
chip = container_of(cp, struct smp2p_chip_dev, gpio);
if (!cp || chip->irq_base <= 0)
return -ENODEV;
return chip->irq_base + offset;
}
/**
* smp2p_gpio_irq_mask_helper - Mask/Unmask interrupt.
*
* @d: IRQ data
* @mask: true to mask (disable), false to unmask (enable)
*/
static void smp2p_gpio_irq_mask_helper(struct irq_data *d, bool mask)
{
struct smp2p_chip_dev *chip;
int offset;
unsigned long flags;
chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
if (!chip || chip->irq_base <= 0)
return;
offset = d->irq - chip->irq_base;
spin_lock_irqsave(&chip->irq_lock, flags);
if (mask)
clear_bit(offset, chip->irq_enabled);
else
set_bit(offset, chip->irq_enabled);
spin_unlock_irqrestore(&chip->irq_lock, flags);
}
/**
* smp2p_gpio_irq_mask - Mask interrupt.
*
* @d: IRQ data
*/
static void smp2p_gpio_irq_mask(struct irq_data *d)
{
smp2p_gpio_irq_mask_helper(d, true);
}
/**
* smp2p_gpio_irq_unmask - Unmask interrupt.
*
* @d: IRQ data
*/
static void smp2p_gpio_irq_unmask(struct irq_data *d)
{
smp2p_gpio_irq_mask_helper(d, false);
}
/**
* smp2p_gpio_irq_set_type - Set interrupt edge type.
*
* @d: IRQ data
* @type: Edge type for interrupt
* @returns 0 for success; < 0 for failure
*/
static int smp2p_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
struct smp2p_chip_dev *chip;
int offset;
unsigned long flags;
int ret = 0;
chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
if (!chip)
return -ENODEV;
if (chip->irq_base <= 0) {
SMP2P_ERR("%s: '%s':%d virqbase %d invalid\n",
__func__, chip->name, chip->remote_pid,
chip->irq_base);
return -ENODEV;
}
offset = d->irq - chip->irq_base;
spin_lock_irqsave(&chip->irq_lock, flags);
clear_bit(offset, chip->irq_rising_edge);
clear_bit(offset, chip->irq_falling_edge);
switch (type) {
case IRQ_TYPE_EDGE_RISING:
set_bit(offset, chip->irq_rising_edge);
break;
case IRQ_TYPE_EDGE_FALLING:
set_bit(offset, chip->irq_falling_edge);
break;
case IRQ_TYPE_NONE:
case IRQ_TYPE_DEFAULT:
case IRQ_TYPE_EDGE_BOTH:
set_bit(offset, chip->irq_rising_edge);
set_bit(offset, chip->irq_falling_edge);
break;
default:
SMP2P_ERR("%s: unsupported interrupt type 0x%x\n",
__func__, type);
ret = -EINVAL;
break;
}
spin_unlock_irqrestore(&chip->irq_lock, flags);
return ret;
}
/**
* smp2p_irq_map - Creates or updates binding of virtual IRQ
*
* @domain_ptr: Interrupt domain pointer
* @virq: Virtual IRQ
* @hw: Hardware IRQ (same as virq for nomap)
* @returns: 0 for success
*/
static int smp2p_irq_map(struct irq_domain *domain_ptr, unsigned int virq,
irq_hw_number_t hw)
{
struct smp2p_chip_dev *chip;
chip = domain_ptr->host_data;
if (!chip) {
SMP2P_ERR("%s: invalid domain ptr %p\n", __func__, domain_ptr);
return -ENODEV;
}
/* map chip structures to device */
irq_set_lockdep_class(virq, &smp2p_gpio_lock_class);
irq_set_chip_and_handler(virq, &smp2p_gpio_irq_chip,
handle_level_irq);
irq_set_chip_data(virq, chip);
set_irq_flags(virq, IRQF_VALID);
return 0;
}
static struct irq_chip smp2p_gpio_irq_chip = {
.name = "smp2p_gpio",
.irq_mask = smp2p_gpio_irq_mask,
.irq_unmask = smp2p_gpio_irq_unmask,
.irq_set_type = smp2p_gpio_irq_set_type,
};
/* No-map interrupt Domain */
static const struct irq_domain_ops smp2p_irq_domain_ops = {
.map = smp2p_irq_map,
};
/**
* msm_summary_irq_handler - Handles inbound entry change notification.
*
* @chip: GPIO chip pointer
* @entry: Change notification data
*
* Whenever an entry changes, this callback is triggered to determine
* which bits changed and if the corresponding interrupts need to be
* triggered.
*/
static void msm_summary_irq_handler(struct smp2p_chip_dev *chip,
struct msm_smp2p_update_notif *entry)
{
int i;
uint32_t cur_val;
uint32_t prev_val;
uint32_t edge;
unsigned long flags;
bool trigger_interrrupt;
bool irq_rising;
bool irq_falling;
cur_val = entry->current_value;
prev_val = entry->previous_value;
if (chip->irq_base <= 0)
return;
SMP2P_GPIO("'%s':%d GPIO Summary IRQ Change %08x->%08x\n",
chip->name, chip->remote_pid, prev_val, cur_val);
for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
spin_lock_irqsave(&chip->irq_lock, flags);
trigger_interrrupt = false;
edge = (prev_val & 0x1) << 1 | (cur_val & 0x1);
irq_rising = test_bit(i, chip->irq_rising_edge);
irq_falling = test_bit(i, chip->irq_falling_edge);
if (test_bit(i, chip->irq_enabled)) {
if (edge == 0x1 && irq_rising)
/* 0->1 transition */
trigger_interrrupt = true;
else if (edge == 0x2 && irq_falling)
/* 1->0 transition */
trigger_interrrupt = true;
} else {
SMP2P_GPIO(
"'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s disabled\n",
chip->name, chip->remote_pid, i,
chip->irq_base + i,
edge_name_rising[irq_rising],
edge_name_falling[irq_falling],
edge_names[edge]);
}
spin_unlock_irqrestore(&chip->irq_lock, flags);
if (trigger_interrrupt) {
SMP2P_GPIO(
"'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s triggering\n",
chip->name, chip->remote_pid, i,
chip->irq_base + i,
edge_name_rising[irq_rising],
edge_name_falling[irq_falling],
edge_names[edge]);
(void)generic_handle_irq(chip->irq_base + i);
}
cur_val >>= 1;
prev_val >>= 1;
}
}
/**
* Adds an interrupt domain based upon the DT node.
*
* @chip: pointer to GPIO chip
* @node: pointer to Device Tree node
*/
static void smp2p_add_irq_domain(struct smp2p_chip_dev *chip,
struct device_node *node)
{
int i;
/* map GPIO pins to interrupts */
chip->irq_domain = irq_domain_add_nomap(node, 0,
&smp2p_irq_domain_ops, chip);
if (!chip->irq_domain) {
SMP2P_ERR("%s: unable to create interrupt domain '%s':%d\n",
__func__, chip->name, chip->remote_pid);
return;
}
for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
unsigned int virt_irq;
virt_irq = irq_create_direct_mapping(chip->irq_domain);
if (virt_irq == NO_IRQ) {
SMP2P_ERR("%s: gpio->virt IRQ mapping failed '%s':%d\n",
__func__, chip->name, chip->remote_pid);
} else if (!chip->irq_base) {
chip->irq_base = virt_irq;
}
}
}
/**
* Notifier function passed into smp2p API for out bound entries.
*
* @self: Pointer to calling notifier block
* @event: Event
* @data: Event-specific data
* @returns: 0
*/
static int smp2p_gpio_out_notify(struct notifier_block *self,
unsigned long event, void *data)
{
struct smp2p_chip_dev *chip;
chip = container_of(self, struct smp2p_chip_dev, out_notifier);
switch (event) {
case SMP2P_OPEN:
chip->is_open = 1;
SMP2P_GPIO("%s: Opened out '%s':%d\n", __func__,
chip->name, chip->remote_pid);
break;
case SMP2P_ENTRY_UPDATE:
break;
default:
SMP2P_ERR("%s: Unknown event\n", __func__);
break;
}
return 0;
}
/**
* Notifier function passed into smp2p API for in bound entries.
*
* @self: Pointer to calling notifier block
* @event: Event
* @data: Event-specific data
* @returns: 0
*/
static int smp2p_gpio_in_notify(struct notifier_block *self,
unsigned long event, void *data)
{
struct smp2p_chip_dev *chip;
chip = container_of(self, struct smp2p_chip_dev, in_notifier);
switch (event) {
case SMP2P_OPEN:
chip->is_open = 1;
SMP2P_GPIO("%s: Opened in '%s':%d\n", __func__,
chip->name, chip->remote_pid);
break;
case SMP2P_ENTRY_UPDATE:
msm_summary_irq_handler(chip, data);
break;
default:
SMP2P_ERR("%s: Unknown event\n", __func__);
break;
}
return 0;
}
/**
* Device tree probe function.
*
* @pdev: Pointer to device tree data.
* @returns: 0 on success; -ENODEV otherwise
*
* Called for each smp2pgpio entry in the device tree.
*/
static int smp2p_gpio_probe(struct platform_device *pdev)
{
struct device_node *node;
char *key;
struct smp2p_chip_dev *chip;
const char *name_tmp;
unsigned long flags;
bool is_test_entry = false;
int ret;
chip = kzalloc(sizeof(struct smp2p_chip_dev), GFP_KERNEL);
if (!chip) {
SMP2P_ERR("%s: out of memory\n", __func__);
ret = -ENOMEM;
goto fail;
}
spin_lock_init(&chip->irq_lock);
/* parse device tree */
node = pdev->dev.of_node;
key = "qcom,entry-name";
ret = of_property_read_string(node, key, &name_tmp);
if (ret) {
SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
goto fail;
}
strlcpy(chip->name, name_tmp, sizeof(chip->name));
key = "qcom,remote-pid";
ret = of_property_read_u32(node, key, &chip->remote_pid);
if (ret) {
SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
goto fail;
}
key = "qcom,is-inbound";
chip->is_inbound = of_property_read_bool(node, key);
/* create virtual GPIO controller */
chip->gpio.label = chip->name;
chip->gpio.dev = &pdev->dev;
chip->gpio.owner = THIS_MODULE;
chip->gpio.direction_input = smp2p_direction_input,
chip->gpio.get = smp2p_get_value;
chip->gpio.direction_output = smp2p_direction_output,
chip->gpio.set = smp2p_set_value;
chip->gpio.to_irq = smp2p_gpio_to_irq,
chip->gpio.base = -1; /* use dynamic GPIO pin allocation */
chip->gpio.ngpio = SMP2P_BITS_PER_ENTRY;
ret = gpiochip_add(&chip->gpio);
if (ret) {
SMP2P_ERR("%s: unable to register GPIO '%s' ret %d\n",
__func__, chip->name, ret);
goto fail;
}
/*
* Test entries opened by GPIO Test conflict with loopback
* support, so the test entries must be explicitly opened
* in the unit test framework.
*/
if (strncmp("smp2p", chip->name, SMP2P_MAX_ENTRY_NAME) == 0)
is_test_entry = true;
if (!chip->is_inbound) {
chip->out_notifier.notifier_call = smp2p_gpio_out_notify;
if (!is_test_entry) {
ret = msm_smp2p_out_open(chip->remote_pid, chip->name,
&chip->out_notifier,
&chip->out_handle);
if (ret < 0)
goto fail;
}
} else {
chip->in_notifier.notifier_call = smp2p_gpio_in_notify;
if (!is_test_entry) {
ret = msm_smp2p_in_register(chip->remote_pid,
chip->name,
&chip->in_notifier);
if (ret < 0)
goto fail;
}
}
spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
list_add(&chip->entry_list, &smp2p_entry_list);
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
/*
* Create interrupt domain - note that chip can't be removed from the
* interrupt domain, so chip cannot be deleted after this point.
*/
if (chip->is_inbound)
smp2p_add_irq_domain(chip, node);
else
chip->irq_base = -1;
SMP2P_GPIO("%s: added %s%s entry '%s':%d gpio %d irq %d",
__func__,
is_test_entry ? "test " : "",
chip->is_inbound ? "in" : "out",
chip->name, chip->remote_pid,
chip->gpio.base, chip->irq_base);
return 0;
fail:
kfree(chip);
return ret;
}
/**
* smp2p_gpio_open_close - Opens or closes entry.
*
* @entry: Entry to open or close
* @do_open: true = open port; false = close
*/
static void smp2p_gpio_open_close(struct smp2p_chip_dev *entry,
bool do_open)
{
int ret;
if (do_open) {
/* open entry */
if (entry->is_inbound)
ret = msm_smp2p_in_register(entry->remote_pid,
entry->name, &entry->in_notifier);
else
ret = msm_smp2p_out_open(entry->remote_pid,
entry->name, &entry->out_notifier,
&entry->out_handle);
SMP2P_GPIO("%s: opened %s '%s':%d ret %d\n",
__func__,
entry->is_inbound ? "in" : "out",
entry->name, entry->remote_pid,
ret);
} else {
/* close entry */
if (entry->is_inbound)
ret = msm_smp2p_in_unregister(entry->remote_pid,
entry->name, &entry->in_notifier);
else
ret = msm_smp2p_out_close(&entry->out_handle);
entry->is_open = false;
SMP2P_GPIO("%s: closed %s '%s':%d ret %d\n",
__func__,
entry->is_inbound ? "in" : "out",
entry->name, entry->remote_pid, ret);
}
}
/**
* smp2p_gpio_open_test_entry - Opens or closes test entries for unit testing.
*
* @name: Name of the entry
* @remote_pid: Remote processor ID
* @do_open: true = open port; false = close
*/
void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open)
{
struct smp2p_chip_dev *entry;
struct smp2p_chip_dev *start_entry;
unsigned long flags;
spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
if (list_empty(&smp2p_entry_list)) {
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
return;
}
start_entry = list_first_entry(&smp2p_entry_list,
struct smp2p_chip_dev,
entry_list);
entry = start_entry;
do {
if (!strncmp(entry->name, name, SMP2P_MAX_ENTRY_NAME)
&& entry->remote_pid == remote_pid) {
/* found entry to change */
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
smp2p_gpio_open_close(entry, do_open);
spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
}
list_rotate_left(&smp2p_entry_list);
entry = list_first_entry(&smp2p_entry_list,
struct smp2p_chip_dev,
entry_list);
} while (entry != start_entry);
spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
}
static struct of_device_id msm_smp2p_match_table[] = {
{.compatible = "qcom,smp2pgpio", },
{},
};
static struct platform_driver smp2p_gpio_driver = {
.probe = smp2p_gpio_probe,
.driver = {
.name = "smp2pgpio",
.owner = THIS_MODULE,
.of_match_table = msm_smp2p_match_table,
},
};
static int smp2p_init(void)
{
INIT_LIST_HEAD(&smp2p_entry_list);
return platform_driver_register(&smp2p_gpio_driver);
}
module_init(smp2p_init);
static void __exit smp2p_exit(void)
{
platform_driver_unregister(&smp2p_gpio_driver);
}
module_exit(smp2p_exit);
MODULE_DESCRIPTION("SMP2P GPIO");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,624 @@
/* arch/arm/mach-msm/smp2p_gpio_test.c
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/completion.h>
#include <linux/irq.h>
#include <linux/bitmap.h>
#include "smp2p_private.h"
#include "smp2p_test_common.h"
/* Interrupt callback data */
struct gpio_info {
int gpio_base_id;
int irq_base_id;
bool initialized;
struct completion cb_completion;
int cb_count;
DECLARE_BITMAP(triggered_irqs, SMP2P_BITS_PER_ENTRY);
};
/* GPIO Inbound/Outbound callback info */
struct gpio_inout {
struct gpio_info in;
struct gpio_info out;
};
static struct gpio_inout gpio_info[SMP2P_NUM_PROCS];
/**
* Init/reset the callback data.
*
* @info: Pointer to callback data
*/
static void cb_data_reset(struct gpio_info *info)
{
int n;
if (!info)
return;
if (!info->initialized) {
init_completion(&info->cb_completion);
info->initialized = true;
}
info->cb_count = 0;
for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n)
clear_bit(n, info->triggered_irqs);
INIT_COMPLETION(info->cb_completion);
}
static int smp2p_gpio_test_probe(struct platform_device *pdev)
{
int id;
int cnt;
struct device_node *node = pdev->dev.of_node;
struct gpio_info *gpio_info_ptr = NULL;
/*
* NOTE: This does a string-lookup of the GPIO pin name and doesn't
* actually directly link to the SMP2P GPIO driver since all
* GPIO/Interrupt access must be through standard
* Linux GPIO / Interrupt APIs.
*/
if (strcmp("qcom,smp2pgpio_test_smp2p_1_in", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_MODEM_PROC].in;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_1_out", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_MODEM_PROC].out;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_2_in", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_AUDIO_PROC].in;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_2_out", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_AUDIO_PROC].out;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_4_in", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_WIRELESS_PROC].in;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_4_out", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_WIRELESS_PROC].out;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_7_in", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
} else if (strcmp("qcom,smp2pgpio_test_smp2p_7_out", node->name) == 0) {
gpio_info_ptr = &gpio_info[SMP2P_REMOTE_MOCK_PROC].out;
} else {
pr_err("%s: unable to match device type '%s'\n",
__func__, node->name);
return -ENODEV;
}
/* retrieve the GPIO and interrupt ID's */
cnt = of_gpio_count(node);
if (cnt && gpio_info_ptr) {
/*
* Instead of looping through all 32-bits, we can just get the
* first pin to get the base IDs. This saves on the verbosity
* of the device tree nodes as well.
*/
id = of_get_gpio(node, 0);
gpio_info_ptr->gpio_base_id = id;
gpio_info_ptr->irq_base_id = gpio_to_irq(id);
}
return 0;
}
/*
* NOTE: Instead of match table and device driver, you may be able to just
* call of_find_compatible_node() in your init function.
*/
static struct of_device_id msm_smp2p_match_table[] = {
/* modem */
{.compatible = "qcom,smp2pgpio_test_smp2p_1_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_1_in", },
/* audio (adsp) */
{.compatible = "qcom,smp2pgpio_test_smp2p_2_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_2_in", },
/* wcnss */
{.compatible = "qcom,smp2pgpio_test_smp2p_4_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_4_in", },
/* mock loopback */
{.compatible = "qcom,smp2pgpio_test_smp2p_7_out", },
{.compatible = "qcom,smp2pgpio_test_smp2p_7_in", },
{},
};
static struct platform_driver smp2p_gpio_driver = {
.probe = smp2p_gpio_test_probe,
.driver = {
.name = "smp2pgpio_test",
.owner = THIS_MODULE,
.of_match_table = msm_smp2p_match_table,
},
};
/**
* smp2p_ut_local_gpio_out - Verify outbound functionality.
*
* @s: pointer to output file
*/
static void smp2p_ut_local_gpio_out(struct seq_file *s)
{
int failed = 0;
struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].out;
int ret;
int id;
struct msm_smp2p_remote_mock *mock;
seq_printf(s, "Running %s\n", __func__);
do {
/* initialize mock edge */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
mock = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(mock, !=, NULL);
mock->rx_interrupt_count = 0;
memset(&mock->remote_item, 0,
sizeof(struct smp2p_smem_item));
smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
0, 1);
strlcpy(mock->remote_item.entries[0].name, "smp2p",
SMP2P_MAX_ENTRY_NAME);
SMP2P_SET_ENT_VALID(
mock->remote_item.header.valid_total_ent, 1);
msm_smp2p_set_remote_mock_exists(true);
mock->tx_interrupt();
/* open GPIO entry */
smp2p_gpio_open_test_entry("smp2p",
SMP2P_REMOTE_MOCK_PROC, true);
/* verify set/get functions */
UT_ASSERT_INT(0, <, cb_info->gpio_base_id);
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
int pin = cb_info->gpio_base_id + id;
mock->rx_interrupt_count = 0;
gpio_set_value(pin, 1);
UT_ASSERT_INT(1, ==, mock->rx_interrupt_count);
UT_ASSERT_INT(1, ==, gpio_get_value(pin));
gpio_set_value(pin, 0);
UT_ASSERT_INT(2, ==, mock->rx_interrupt_count);
UT_ASSERT_INT(0, ==, gpio_get_value(pin));
}
if (failed)
break;
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
}
smp2p_gpio_open_test_entry("smp2p",
SMP2P_REMOTE_MOCK_PROC, false);
}
/**
* smp2p_gpio_irq - Interrupt handler for inbound entries.
*
* @irq: Virtual IRQ being triggered
* @data: Cookie data (struct gpio_info * in this case)
* @returns: Number of bytes written
*/
static irqreturn_t smp2p_gpio_irq(int irq, void *data)
{
struct gpio_info *gpio_ptr = (struct gpio_info *)data;
int offset;
if (!gpio_ptr) {
pr_err("%s: gpio_ptr is NULL for irq %d\n", __func__, irq);
return IRQ_HANDLED;
}
offset = irq - gpio_ptr->irq_base_id;
if (offset >= 0 && offset < SMP2P_BITS_PER_ENTRY)
set_bit(offset, gpio_ptr->triggered_irqs);
else
pr_err("%s: invalid irq offset base %d; irq %d\n",
__func__, gpio_ptr->irq_base_id, irq);
++gpio_ptr->cb_count;
complete(&gpio_ptr->cb_completion);
return IRQ_HANDLED;
}
/**
* smp2p_ut_local_gpio_in - Verify inbound functionality.
*
* @s: pointer to output file
*/
static void smp2p_ut_local_gpio_in(struct seq_file *s)
{
int failed = 0;
struct gpio_info *cb_info = &gpio_info[SMP2P_REMOTE_MOCK_PROC].in;
int id;
int ret;
int virq;
struct msm_smp2p_remote_mock *mock;
seq_printf(s, "Running %s\n", __func__);
cb_data_reset(cb_info);
do {
/* initialize mock edge */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
mock = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(mock, !=, NULL);
mock->rx_interrupt_count = 0;
memset(&mock->remote_item, 0,
sizeof(struct smp2p_smem_item));
smp2p_init_header((struct smp2p_smem *)&mock->remote_item,
SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
0, 1);
strlcpy(mock->remote_item.entries[0].name, "smp2p",
SMP2P_MAX_ENTRY_NAME);
SMP2P_SET_ENT_VALID(
mock->remote_item.header.valid_total_ent, 1);
msm_smp2p_set_remote_mock_exists(true);
mock->tx_interrupt();
smp2p_gpio_open_test_entry("smp2p",
SMP2P_REMOTE_MOCK_PROC, true);
/* verify set/get functions locally */
UT_ASSERT_INT(0, <, cb_info->gpio_base_id);
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
int pin;
int current_value;
/* verify pin value cannot be set */
pin = cb_info->gpio_base_id + id;
current_value = gpio_get_value(pin);
gpio_set_value(pin, 0);
UT_ASSERT_INT(current_value, ==, gpio_get_value(pin));
gpio_set_value(pin, 1);
UT_ASSERT_INT(current_value, ==, gpio_get_value(pin));
/* verify no interrupts */
UT_ASSERT_INT(0, ==, cb_info->cb_count);
}
if (failed)
break;
/* register for interrupts */
UT_ASSERT_INT(0, <, cb_info->irq_base_id);
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
virq = cb_info->irq_base_id + id;
UT_ASSERT_INT(0, >, (unsigned int)irq_to_desc(virq));
ret = request_irq(virq,
smp2p_gpio_irq, IRQF_TRIGGER_RISING,
"smp2p_test", cb_info);
UT_ASSERT_INT(0, ==, ret);
}
if (failed)
break;
/* verify both rising and falling edge interrupts */
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
virq = cb_info->irq_base_id + id;
irq_set_irq_type(virq, IRQ_TYPE_EDGE_BOTH);
cb_data_reset(cb_info);
/* verify rising-edge interrupt */
mock->remote_item.entries[0].entry = 1 << id;
mock->tx_interrupt();
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
UT_ASSERT_INT(0, <,
test_bit(id, cb_info->triggered_irqs));
test_bit(id, cb_info->triggered_irqs);
/* verify falling-edge interrupt */
mock->remote_item.entries[0].entry = 0;
mock->tx_interrupt();
UT_ASSERT_INT(cb_info->cb_count, ==, 2);
UT_ASSERT_INT(0, <,
test_bit(id, cb_info->triggered_irqs));
}
if (failed)
break;
/* verify rising-edge interrupts */
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
virq = cb_info->irq_base_id + id;
irq_set_irq_type(virq, IRQ_TYPE_EDGE_RISING);
cb_data_reset(cb_info);
/* verify only rising-edge interrupt is triggered */
mock->remote_item.entries[0].entry = 1 << id;
mock->tx_interrupt();
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
UT_ASSERT_INT(0, <,
test_bit(id, cb_info->triggered_irqs));
test_bit(id, cb_info->triggered_irqs);
mock->remote_item.entries[0].entry = 0;
mock->tx_interrupt();
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
UT_ASSERT_INT(0, <,
test_bit(id, cb_info->triggered_irqs));
}
if (failed)
break;
/* verify falling-edge interrupts */
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
virq = cb_info->irq_base_id + id;
irq_set_irq_type(virq, IRQ_TYPE_EDGE_FALLING);
cb_data_reset(cb_info);
/* verify only rising-edge interrupt is triggered */
mock->remote_item.entries[0].entry = 1 << id;
mock->tx_interrupt();
UT_ASSERT_INT(cb_info->cb_count, ==, 0);
UT_ASSERT_INT(0, ==,
test_bit(id, cb_info->triggered_irqs));
mock->remote_item.entries[0].entry = 0;
mock->tx_interrupt();
UT_ASSERT_INT(cb_info->cb_count, ==, 1);
UT_ASSERT_INT(0, <,
test_bit(id, cb_info->triggered_irqs));
}
if (failed)
break;
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
}
/* unregister for interrupts */
if (cb_info->irq_base_id) {
for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
free_irq(cb_info->irq_base_id + id, cb_info);
}
smp2p_gpio_open_test_entry("smp2p",
SMP2P_REMOTE_MOCK_PROC, false);
}
/**
* smp2p_gpio_write_bits - writes value to each GPIO pin specified in mask.
*
* @gpio: gpio test structure
* @mask: 1 = write gpio_value to this GPIO pin
* @gpio_value: value to write to GPIO pin
*/
static void smp2p_gpio_write_bits(struct gpio_info *gpio, uint32_t mask,
int gpio_value)
{
int n;
for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n) {
if (mask & 0x1)
gpio_set_value(gpio->gpio_base_id + n, gpio_value);
mask >>= 1;
}
}
static void smp2p_gpio_set_bits(struct gpio_info *gpio, uint32_t mask)
{
smp2p_gpio_write_bits(gpio, mask, 1);
}
static void smp2p_gpio_clr_bits(struct gpio_info *gpio, uint32_t mask)
{
smp2p_gpio_write_bits(gpio, mask, 0);
}
/**
* smp2p_gpio_get_value - reads entire 32-bits of GPIO
*
* @gpio: gpio structure
* @returns: 32 bit value of GPIO pins
*/
static uint32_t smp2p_gpio_get_value(struct gpio_info *gpio)
{
int n;
uint32_t value = 0;
for (n = 0; n < SMP2P_BITS_PER_ENTRY; ++n) {
if (gpio_get_value(gpio->gpio_base_id + n))
value |= 1 << n;
}
return value;
}
/**
* smp2p_ut_remote_inout_core - Verify inbound/outbound functionality.
*
* @s: pointer to output file
* @remote_pid: Remote processor to test
* @name: Name of the test for reporting
*
* This test verifies inbound/outbound functionality for the remote processor.
*/
static void smp2p_ut_remote_inout_core(struct seq_file *s, int remote_pid,
const char *name)
{
int failed = 0;
uint32_t request;
uint32_t response;
struct gpio_info *cb_in;
struct gpio_info *cb_out;
int id;
int ret;
seq_printf(s, "Running %s for '%s' remote pid %d\n",
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
cb_in = &gpio_info[remote_pid].in;
cb_out = &gpio_info[remote_pid].out;
cb_data_reset(cb_in);
cb_data_reset(cb_out);
do {
/* open test entries */
msm_smp2p_deinit_rmt_lpb_proc(remote_pid);
smp2p_gpio_open_test_entry("smp2p", remote_pid, true);
/* register for interrupts */
UT_ASSERT_INT(0, <, cb_in->gpio_base_id);
UT_ASSERT_INT(0, <, cb_in->irq_base_id);
for (id = 0; id < SMP2P_BITS_PER_ENTRY && !failed; ++id) {
int virq = cb_in->irq_base_id + id;
UT_ASSERT_INT(0, >, (unsigned int)irq_to_desc(virq));
ret = request_irq(virq,
smp2p_gpio_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"smp2p_test", cb_in);
UT_ASSERT_INT(0, ==, ret);
}
if (failed)
break;
/* write echo of data value 0 */
UT_ASSERT_INT(0, <, cb_out->gpio_base_id);
request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(request, 1);
SMP2P_SET_RMT_CMD(request, SMP2P_LB_CMD_ECHO);
SMP2P_SET_RMT_DATA(request, 0x0);
smp2p_gpio_set_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
smp2p_gpio_clr_bits(cb_out, ~SMP2P_RMT_IGNORE_MASK);
smp2p_gpio_set_bits(cb_out, request);
UT_ASSERT_INT(cb_in->cb_count, ==, 0);
smp2p_gpio_clr_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
/* verify response */
do {
/* wait for up to 32 changes */
if (wait_for_completion_timeout(
&cb_in->cb_completion, HZ / 2) == 0)
break;
INIT_COMPLETION(cb_in->cb_completion);
} while (cb_in->cb_count < 32);
UT_ASSERT_INT(cb_in->cb_count, >, 0);
response = smp2p_gpio_get_value(cb_in);
SMP2P_SET_RMT_CMD_TYPE(request, 0);
UT_ASSERT_HEX(request, ==, response);
/* write echo of data value of all 1's */
request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(request, 1);
SMP2P_SET_RMT_CMD(request, SMP2P_LB_CMD_ECHO);
SMP2P_SET_RMT_DATA(request, ~0);
smp2p_gpio_set_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
cb_data_reset(cb_in);
smp2p_gpio_clr_bits(cb_out, ~SMP2P_RMT_IGNORE_MASK);
smp2p_gpio_set_bits(cb_out, request);
UT_ASSERT_INT(cb_in->cb_count, ==, 0);
smp2p_gpio_clr_bits(cb_out, SMP2P_RMT_IGNORE_MASK);
/* verify response including 24 interrupts */
do {
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in->cb_completion, HZ / 2),
>, 0);
INIT_COMPLETION(cb_in->cb_completion);
} while (cb_in->cb_count < 24);
response = smp2p_gpio_get_value(cb_in);
SMP2P_SET_RMT_CMD_TYPE(request, 0);
UT_ASSERT_HEX(request, ==, response);
UT_ASSERT_INT(24, ==, cb_in->cb_count);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", name);
seq_printf(s, "\tFailed\n");
}
/* unregister for interrupts */
if (cb_in->irq_base_id) {
for (id = 0; id < SMP2P_BITS_PER_ENTRY; ++id)
free_irq(cb_in->irq_base_id + id, cb_in);
}
smp2p_gpio_open_test_entry("smp2p", remote_pid, false);
msm_smp2p_init_rmt_lpb_proc(remote_pid);
}
/**
* smp2p_ut_remote_inout - Verify inbound/outbound functionality for all.
*
* @s: pointer to output file
*
* This test verifies inbound and outbound functionality for all
* configured remote processor.
*/
static void smp2p_ut_remote_inout(struct seq_file *s)
{
struct smp2p_interrupt_config *int_cfg;
int pid;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg) {
seq_printf(s, "Remote processor config unavailable\n");
return;
}
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
if (!int_cfg[pid].is_configured)
continue;
smp2p_ut_remote_inout_core(s, pid, __func__);
}
}
static int __init smp2p_debugfs_init(void)
{
/* register GPIO pins */
(void)platform_driver_register(&smp2p_gpio_driver);
/*
* Add Unit Test entries.
*
* The idea with unit tests is that you can run all of them
* from ADB shell by doing:
* adb shell
* cat ut*
*
* And if particular tests fail, you can then repeatedly run the
* failing tests as you debug and resolve the failing test.
*/
smp2p_debug_create("ut_local_gpio_out", smp2p_ut_local_gpio_out);
smp2p_debug_create("ut_local_gpio_in", smp2p_ut_local_gpio_in);
smp2p_debug_create("ut_remote_gpio_inout", smp2p_ut_remote_inout);
return 0;
}
late_initcall(smp2p_debugfs_init);

View File

@ -0,0 +1,440 @@
/* arch/arm/mach-msm/smp2p_loopback.c
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/debugfs.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/termios.h>
#include <linux/module.h>
#include <linux/remote_spinlock.h>
#include "smd_private.h"
#include "smp2p_private.h"
/**
* struct smp2p_loopback_ctx - Representation of remote loopback object.
*
* @proc_id: Processor id of the processor that sends the loopback commands.
* @out: Handle to the smem entry structure for providing the response.
* @out_nb: Notifies the opening of local entry.
* @out_is_active: Outbound entry events should be processed.
* @in_nb: Notifies changes in the remote entry.
* @in_is_active: Inbound entry events should be processed.
* @rmt_lpb_work: Work item that handles the incoming loopback commands.
* @rmt_cmd: Structure that holds the current and previous value of the entry.
*/
struct smp2p_loopback_ctx {
int proc_id;
struct msm_smp2p_out *out;
struct notifier_block out_nb;
bool out_is_active;
struct notifier_block in_nb;
bool in_is_active;
struct work_struct rmt_lpb_work;
struct msm_smp2p_update_notif rmt_cmd;
};
static struct smp2p_loopback_ctx remote_loopback[SMP2P_NUM_PROCS];
static struct msm_smp2p_remote_mock remote_mock;
/**
* remote_spinlock_test - Handles remote spinlock test.
*
* @ctx: Loopback context
*/
static void remote_spinlock_test(struct smp2p_loopback_ctx *ctx)
{
uint32_t test_request;
uint32_t test_response;
unsigned long flags;
int n;
unsigned lock_count = 0;
remote_spinlock_t *smem_spinlock;
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
smem_spinlock = smem_get_remote_spinlock();
if (!smem_spinlock) {
pr_err("%s: unable to get remote spinlock\n", __func__);
return;
}
for (;;) {
remote_spin_lock_irqsave(smem_spinlock, flags);
++lock_count;
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_LOCKED);
(void)msm_smp2p_out_write(ctx->out, test_request);
for (n = 0; n < 10000; ++n) {
(void)msm_smp2p_in_read(ctx->proc_id,
"smp2p", &test_response);
test_response = SMP2P_GET_RMT_CMD(test_response);
if (test_response == SMP2P_LB_CMD_RSPIN_END)
break;
if (test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED)
SMP2P_ERR("%s: invalid spinlock command %x\n",
__func__, test_response);
}
if (test_response == SMP2P_LB_CMD_RSPIN_END) {
SMP2P_SET_RMT_CMD_TYPE_RESP(test_request);
SMP2P_SET_RMT_CMD(test_request,
SMP2P_LB_CMD_RSPIN_END);
SMP2P_SET_RMT_DATA(test_request, lock_count);
(void)msm_smp2p_out_write(ctx->out, test_request);
break;
}
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_UNLOCKED);
(void)msm_smp2p_out_write(ctx->out, test_request);
remote_spin_unlock_irqrestore(smem_spinlock, flags);
}
remote_spin_unlock_irqrestore(smem_spinlock, flags);
}
/**
* smp2p_rmt_lpb_worker - Handles incoming remote loopback commands.
*
* @work: Work Item scheduled to handle the incoming commands.
*/
static void smp2p_rmt_lpb_worker(struct work_struct *work)
{
struct smp2p_loopback_ctx *ctx;
int lpb_cmd;
int lpb_cmd_type;
int lpb_data;
ctx = container_of(work, struct smp2p_loopback_ctx, rmt_lpb_work);
if (!ctx->in_is_active || !ctx->out_is_active)
return;
if (ctx->rmt_cmd.previous_value == ctx->rmt_cmd.current_value)
return;
lpb_cmd_type = SMP2P_GET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value);
lpb_cmd = SMP2P_GET_RMT_CMD(ctx->rmt_cmd.current_value);
lpb_data = SMP2P_GET_RMT_DATA(ctx->rmt_cmd.current_value);
if (lpb_cmd & SMP2P_RLPB_IGNORE)
return;
switch (lpb_cmd) {
case SMP2P_LB_CMD_NOOP:
/* Do nothing */
break;
case SMP2P_LB_CMD_ECHO:
SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0);
SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value,
lpb_data);
(void)msm_smp2p_out_write(ctx->out,
ctx->rmt_cmd.current_value);
break;
case SMP2P_LB_CMD_CLEARALL:
ctx->rmt_cmd.current_value = 0;
(void)msm_smp2p_out_write(ctx->out,
ctx->rmt_cmd.current_value);
break;
case SMP2P_LB_CMD_PINGPONG:
SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0);
if (lpb_data) {
lpb_data--;
SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value,
lpb_data);
(void)msm_smp2p_out_write(ctx->out,
ctx->rmt_cmd.current_value);
}
break;
case SMP2P_LB_CMD_RSPIN_START:
remote_spinlock_test(ctx);
break;
case SMP2P_LB_CMD_RSPIN_LOCKED:
case SMP2P_LB_CMD_RSPIN_UNLOCKED:
case SMP2P_LB_CMD_RSPIN_END:
/* not used for remote spinlock test */
break;
default:
SMP2P_DBG("%s: Unknown loopback command %x\n",
__func__, lpb_cmd);
break;
}
}
/**
* smp2p_rmt_in_edge_notify - Schedules a work item to handle the commands.
*
* @nb: Notifier block, this is called when the value in remote entry changes.
* @event: Takes value SMP2P_ENTRY_UPDATE or SMP2P_OPEN based on the event.
* @data: Consists of previous and current value in case of entry update.
* @returns: 0 for success (return value required for notifier chains).
*/
static int smp2p_rmt_in_edge_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
struct smp2p_loopback_ctx *ctx;
if (!(event == SMP2P_ENTRY_UPDATE || event == SMP2P_OPEN))
return 0;
ctx = container_of(nb, struct smp2p_loopback_ctx, in_nb);
if (data && ctx->in_is_active) {
ctx->rmt_cmd =
*(struct msm_smp2p_update_notif *)data;
schedule_work(&ctx->rmt_lpb_work);
}
return 0;
}
/**
* smp2p_rmt_out_edge_notify - Notifies on the opening of the outbound entry.
*
* @nb: Notifier block, this is called when the local entry is open.
* @event: Takes on value SMP2P_OPEN when the local entry is open.
* @data: Consist of current value of the remote entry, if entry is open.
* @returns: 0 for success (return value required for notifier chains).
*/
static int smp2p_rmt_out_edge_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
struct smp2p_loopback_ctx *ctx;
ctx = container_of(nb, struct smp2p_loopback_ctx, out_nb);
if (event == SMP2P_OPEN)
SMP2P_DBG("%s: 'smp2p':%d opened\n", __func__,
ctx->proc_id);
return 0;
}
/**
* msm_smp2p_init_rmt_lpb - Initializes the remote loopback object.
*
* @ctx: Pointer to remote loopback object that needs to be initialized.
* @pid: Processor id of the processor that is sending the commands.
* @entry: Name of the entry that needs to be opened locally.
* @returns: 0 on success, standard Linux error code otherwise.
*/
static int msm_smp2p_init_rmt_lpb(struct smp2p_loopback_ctx *ctx,
int pid, const char *entry)
{
int ret = 0;
int tmp;
if (!ctx || !entry || pid > SMP2P_NUM_PROCS)
return -EINVAL;
ctx->in_nb.notifier_call = smp2p_rmt_in_edge_notify;
ctx->out_nb.notifier_call = smp2p_rmt_out_edge_notify;
ctx->proc_id = pid;
ctx->in_is_active = true;
ctx->out_is_active = true;
tmp = msm_smp2p_out_open(pid, entry, &ctx->out_nb,
&ctx->out);
if (tmp) {
SMP2P_ERR("%s: open failed outbound entry '%s':%d - ret %d\n",
__func__, entry, pid, tmp);
ret = tmp;
}
tmp = msm_smp2p_in_register(ctx->proc_id,
SMP2P_RLPB_ENTRY_NAME,
&ctx->in_nb);
if (tmp) {
SMP2P_ERR("%s: unable to open inbound entry '%s':%d - ret %d\n",
__func__, entry, pid, tmp);
ret = tmp;
}
return ret;
}
/**
* msm_smp2p_init_rmt_lpb_proc - Wrapper over msm_smp2p_init_rmt_lpb
*
* @remote_pid: Processor ID of the processor that sends loopback command.
* @returns: Pointer to outbound entry handle.
*/
void *msm_smp2p_init_rmt_lpb_proc(int remote_pid)
{
int tmp;
void *ret = NULL;
tmp = msm_smp2p_init_rmt_lpb(&remote_loopback[remote_pid],
remote_pid, SMP2P_RLPB_ENTRY_NAME);
if (!tmp)
ret = remote_loopback[remote_pid].out;
return ret;
}
EXPORT_SYMBOL(msm_smp2p_init_rmt_lpb_proc);
/**
* msm_smp2p_deinit_rmt_lpb_proc - Unregister support for remote processor.
*
* @remote_pid: Processor ID of the remote system.
* @returns: 0 on success, standard Linux error code otherwise.
*
* Unregister loopback support for remote processor.
*/
int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid)
{
int ret = 0;
int tmp;
struct smp2p_loopback_ctx *ctx;
if (remote_pid >= SMP2P_NUM_PROCS)
return -EINVAL;
ctx = &remote_loopback[remote_pid];
/* abort any pending notifications */
remote_loopback[remote_pid].out_is_active = false;
remote_loopback[remote_pid].in_is_active = false;
flush_work(&ctx->rmt_lpb_work);
/* unregister entries */
tmp = msm_smp2p_out_close(&remote_loopback[remote_pid].out);
remote_loopback[remote_pid].out = NULL;
if (tmp) {
SMP2P_ERR("%s: outbound 'smp2p':%d close failed %d\n",
__func__, remote_pid, tmp);
ret = tmp;
}
tmp = msm_smp2p_in_unregister(remote_pid,
SMP2P_RLPB_ENTRY_NAME, &remote_loopback[remote_pid].in_nb);
if (tmp) {
SMP2P_ERR("%s: inbound 'smp2p':%d close failed %d\n",
__func__, remote_pid, tmp);
ret = tmp;
}
return ret;
}
EXPORT_SYMBOL(msm_smp2p_deinit_rmt_lpb_proc);
/**
* msm_smp2p_set_remote_mock_exists - Sets the remote mock configuration.
*
* @item_exists: true = Remote mock SMEM item exists
*
* This is used in the testing environment to simulate the existence of the
* remote smem item in order to test the negotiation algorithm.
*/
void msm_smp2p_set_remote_mock_exists(bool item_exists)
{
remote_mock.item_exists = item_exists;
}
EXPORT_SYMBOL(msm_smp2p_set_remote_mock_exists);
/**
* msm_smp2p_get_remote_mock - Get remote mock object.
*
* @returns: Point to the remote mock object.
*/
void *msm_smp2p_get_remote_mock(void)
{
return &remote_mock;
}
EXPORT_SYMBOL(msm_smp2p_get_remote_mock);
/**
* msm_smp2p_get_remote_mock_smem_item - Returns a pointer to remote item.
*
* @size: Size of item.
* @returns: Pointer to mock remote smem item.
*/
void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size)
{
void *ptr = NULL;
if (remote_mock.item_exists) {
*size = sizeof(remote_mock.remote_item);
ptr = &(remote_mock.remote_item);
}
return ptr;
}
EXPORT_SYMBOL(msm_smp2p_get_remote_mock_smem_item);
/**
* smp2p_remote_mock_rx_interrupt - Triggers receive interrupt for mock proc.
*
* @returns: 0 for success
*
* This function simulates the receiving of interrupt by the mock remote
* processor in a testing environment.
*/
int smp2p_remote_mock_rx_interrupt(void)
{
remote_mock.rx_interrupt_count++;
if (remote_mock.initialized)
complete(&remote_mock.cb_completion);
return 0;
}
EXPORT_SYMBOL(smp2p_remote_mock_rx_interrupt);
/**
* smp2p_remote_mock_tx_interrupt - Calls the SMP2P interrupt handler.
*
* This function calls the interrupt handler of the Apps processor to simulate
* receiving interrupts from a remote processor.
*/
static void smp2p_remote_mock_tx_interrupt(void)
{
msm_smp2p_interrupt_handler(SMP2P_REMOTE_MOCK_PROC);
}
/**
* smp2p_remote_mock_init - Initialize the remote mock and loopback objects.
*
* @returns: 0 for success
*/
static int __init smp2p_remote_mock_init(void)
{
int i;
smp2p_init_header(&remote_mock.remote_item.header,
SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC,
0, 0);
remote_mock.rx_interrupt_count = 0;
remote_mock.rx_interrupt = smp2p_remote_mock_rx_interrupt;
remote_mock.tx_interrupt = smp2p_remote_mock_tx_interrupt;
remote_mock.item_exists = false;
init_completion(&remote_mock.cb_completion);
remote_mock.initialized = true;
for (i = 0; i < SMP2P_NUM_PROCS; i++) {
INIT_WORK(&(remote_loopback[i].rmt_lpb_work),
smp2p_rmt_lpb_worker);
if (i == SMP2P_REMOTE_MOCK_PROC)
/* do not register loopback for remote mock proc */
continue;
msm_smp2p_init_rmt_lpb(&remote_loopback[i],
i, SMP2P_RLPB_ENTRY_NAME);
}
return 0;
}
module_init(smp2p_remote_mock_init);

View File

@ -0,0 +1,226 @@
/* arch/arm/mach-msm/smp2p_private.h
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _ARCH_ARM_MACH_MSM_MSM_SMP2P_PRIVATE_H_
#define _ARCH_ARM_MACH_MSM_MSM_SMP2P_PRIVATE_H_
#include <linux/types.h>
#include <linux/spinlock.h>
#include <mach/msm_ipc_logging.h>
#include "smp2p_private_api.h"
#define SMP2P_MAX_ENTRY 16
#define SMP2P_LOCAL_VERSION 1
#define SMP2P_LOCAL_FEATURE 0x0
/* SMEM Item Header Macros */
#define SMP2P_MAGIC 0x504D5324
#define SMP2P_LOCAL_PID_MASK 0x0000ffff
#define SMP2P_LOCAL_PID_BIT 0
#define SMP2P_REMOTE_PID_MASK 0xffff0000
#define SMP2P_REMOTE_PID_BIT 16
#define SMP2P_VERSION_MASK 0x000000ff
#define SMP2P_VERSION_BIT 0
#define SMP2P_FEATURE_MASK 0xffffff00
#define SMP2P_FEATURE_BIT 8
#define SMP2P_ENT_TOTAL_MASK 0x0000ffff
#define SMP2P_ENT_TOTAL_BIT 0
#define SMP2P_ENT_VALID_MASK 0xffff0000
#define SMP2P_ENT_VALID_BIT 16
#define SMP2P_GET_BITS(hdr_val, mask, bit) \
(((hdr_val) & (mask)) >> (bit))
#define SMP2P_SET_BITS(hdr_val, mask, bit, new_value) \
do {\
hdr_val = (hdr_val & ~(mask)) \
| (((new_value) << (bit)) & (mask)); \
} while (0)
#define SMP2P_GET_LOCAL_PID(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_LOCAL_PID_MASK, SMP2P_LOCAL_PID_BIT)
#define SMP2P_SET_LOCAL_PID(hdr, pid) \
SMP2P_SET_BITS(hdr, SMP2P_LOCAL_PID_MASK, SMP2P_LOCAL_PID_BIT, pid)
#define SMP2P_GET_REMOTE_PID(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_REMOTE_PID_MASK, SMP2P_REMOTE_PID_BIT)
#define SMP2P_SET_REMOTE_PID(hdr, pid) \
SMP2P_SET_BITS(hdr, SMP2P_REMOTE_PID_MASK, SMP2P_REMOTE_PID_BIT, pid)
#define SMP2P_GET_VERSION(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_VERSION_MASK, SMP2P_VERSION_BIT)
#define SMP2P_SET_VERSION(hdr, version) \
SMP2P_SET_BITS(hdr, SMP2P_VERSION_MASK, SMP2P_VERSION_BIT, version)
#define SMP2P_GET_FEATURES(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_FEATURE_MASK, SMP2P_FEATURE_BIT)
#define SMP2P_SET_FEATURES(hdr, features) \
SMP2P_SET_BITS(hdr, SMP2P_FEATURE_MASK, SMP2P_FEATURE_BIT, features)
#define SMP2P_GET_ENT_TOTAL(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_ENT_TOTAL_MASK, SMP2P_ENT_TOTAL_BIT)
#define SMP2P_SET_ENT_TOTAL(hdr, entries) \
SMP2P_SET_BITS(hdr, SMP2P_ENT_TOTAL_MASK, SMP2P_ENT_TOTAL_BIT, entries)
#define SMP2P_GET_ENT_VALID(hdr) \
SMP2P_GET_BITS(hdr, SMP2P_ENT_VALID_MASK, SMP2P_ENT_VALID_BIT)
#define SMP2P_SET_ENT_VALID(hdr, entries) \
SMP2P_SET_BITS(hdr, SMP2P_ENT_VALID_MASK, SMP2P_ENT_VALID_BIT,\
entries)
/* Loopback Command Macros */
#define SMP2P_RMT_CMD_TYPE_MASK 0x80000000
#define SMP2P_RMT_CMD_TYPE_BIT 31
#define SMP2P_RMT_IGNORE_MASK 0x40000000
#define SMP2P_RMT_IGNORE_BIT 30
#define SMP2P_RMT_CMD_MASK 0x3f000000
#define SMP2P_RMT_CMD_BIT 24
#define SMP2P_RMT_DATA_MASK 0x00ffffff
#define SMP2P_RMT_DATA_BIT 0
#define SMP2P_GET_RMT_CMD_TYPE(val) \
SMP2P_GET_BITS(val, SMP2P_RMT_CMD_TYPE_MASK, SMP2P_RMT_CMD_TYPE_BIT)
#define SMP2P_GET_RMT_CMD(val) \
SMP2P_GET_BITS(val, SMP2P_RMT_CMD_MASK, SMP2P_RMT_CMD_BIT)
#define SMP2P_GET_RMT_DATA(val) \
SMP2P_GET_BITS(val, SMP2P_RMT_DATA_MASK, SMP2P_RMT_DATA_BIT)
#define SMP2P_SET_RMT_CMD_TYPE(val, cmd_type) \
SMP2P_SET_BITS(val, SMP2P_RMT_CMD_TYPE_MASK, SMP2P_RMT_CMD_TYPE_BIT, \
cmd_type)
#define SMP2P_SET_RMT_CMD_TYPE_REQ(val) \
SMP2P_SET_RMT_CMD_TYPE(val, 1)
#define SMP2P_SET_RMT_CMD_TYPE_RESP(val) \
SMP2P_SET_RMT_CMD_TYPE(val, 0)
#define SMP2P_SET_RMT_CMD(val, cmd) \
SMP2P_SET_BITS(val, SMP2P_RMT_CMD_MASK, SMP2P_RMT_CMD_BIT, \
cmd)
#define SMP2P_SET_RMT_DATA(val, data) \
SMP2P_SET_BITS(val, SMP2P_RMT_DATA_MASK, SMP2P_RMT_DATA_BIT, data)
enum {
SMP2P_LB_CMD_NOOP = 0x0,
SMP2P_LB_CMD_ECHO,
SMP2P_LB_CMD_CLEARALL,
SMP2P_LB_CMD_PINGPONG,
SMP2P_LB_CMD_RSPIN_START,
SMP2P_LB_CMD_RSPIN_LOCKED,
SMP2P_LB_CMD_RSPIN_UNLOCKED,
SMP2P_LB_CMD_RSPIN_END,
};
#define SMP2P_RLPB_IGNORE 0x40
#define SMP2P_RLPB_ENTRY_NAME "smp2p"
/* Debug Logging Macros */
enum {
MSM_SMP2P_INFO = 1U << 0,
MSM_SMP2P_DEBUG = 1U << 1,
MSM_SMP2P_GPIO = 1U << 2,
};
#define SMP2P_IPC_LOG_STR(x...) do { \
if (smp2p_get_log_ctx()) \
ipc_log_string(smp2p_get_log_ctx(), x); \
} while (0)
#define SMP2P_DBG(x...) do { \
if (smp2p_get_debug_mask() & MSM_SMP2P_DEBUG) \
SMP2P_IPC_LOG_STR(x); \
} while (0)
#define SMP2P_INFO(x...) do { \
if (smp2p_get_debug_mask() & MSM_SMP2P_INFO) \
SMP2P_IPC_LOG_STR(x); \
} while (0)
#define SMP2P_ERR(x...) do { \
pr_err(x); \
SMP2P_IPC_LOG_STR(x); \
} while (0)
#define SMP2P_GPIO(x...) do { \
if (smp2p_get_debug_mask() & MSM_SMP2P_GPIO) \
SMP2P_IPC_LOG_STR(x); \
} while (0)
enum msm_smp2p_edge_state {
SMP2P_EDGE_STATE_CLOSED,
SMP2P_EDGE_STATE_OPENING,
SMP2P_EDGE_STATE_OPENED,
SMP2P_EDGE_STATE_FAILED = 0xff,
};
struct smp2p_smem {
uint32_t magic;
uint32_t feature_version;
uint32_t rem_loc_proc_id;
uint32_t valid_total_ent;
uint32_t reserved;
};
struct smp2p_entry_v1 {
char name[SMP2P_MAX_ENTRY_NAME];
uint32_t entry;
};
struct smp2p_smem_item {
struct smp2p_smem header;
struct smp2p_entry_v1 entries[SMP2P_MAX_ENTRY];
};
/* Mock object for internal loopback testing. */
struct msm_smp2p_remote_mock {
struct smp2p_smem_item remote_item;
int rx_interrupt_count;
int (*rx_interrupt)(void);
void (*tx_interrupt)(void);
bool item_exists;
bool initialized;
struct completion cb_completion;
};
void smp2p_init_header(struct smp2p_smem *header_ptr, int local_pid,
int remote_pid, uint32_t features, uint32_t version);
void *msm_smp2p_get_remote_mock(void);
int smp2p_remote_mock_rx_interrupt(void);
int smp2p_reset_mock_edge(void);
void msm_smp2p_interrupt_handler(int);
void msm_smp2p_set_remote_mock_exists(bool item_exists);
void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size);
void *msm_smp2p_init_rmt_lpb_proc(int remote_pid);
int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid);
void *smp2p_get_log_ctx(void);
int smp2p_get_debug_mask(void);
/* Inbound / outbound Interrupt configuration. */
struct smp2p_interrupt_config {
bool is_configured;
uint32_t *out_int_ptr;
uint32_t out_int_mask;
int in_int_id;
const char *name;
/* interrupt stats */
unsigned in_interrupt_count;
unsigned out_interrupt_count;
};
struct smp2p_interrupt_config *smp2p_get_interrupt_config(void);
const char *smp2p_pid_to_name(int remote_pid);
struct smp2p_smem *smp2p_get_in_item(int remote_pid);
struct smp2p_smem *smp2p_get_out_item(int remote_pid, int *state);
void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open);
#endif

View File

@ -0,0 +1,79 @@
/* arch/arm/mach-msm/smp2p_private_api.h
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_
#define _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_
#include <linux/notifier.h>
struct msm_smp2p_out;
/* Maximum size of the entry name and trailing null. */
#define SMP2P_MAX_ENTRY_NAME 16
/* Bits per entry */
#define SMP2P_BITS_PER_ENTRY 32
/* Processor ID's */
enum {
SMP2P_APPS_PROC = 0,
SMP2P_MODEM_PROC = 1,
SMP2P_AUDIO_PROC = 2,
SMP2P_RESERVED_PROC_1 = 3,
SMP2P_WIRELESS_PROC = 4,
SMP2P_RESERVED_PROC_2 = 5,
SMP2P_POWER_PROC = 6,
/* add new processors here */
SMP2P_REMOTE_MOCK_PROC,
SMP2P_NUM_PROCS,
};
/**
* Notification events that are passed to notifier for incoming and outgoing
* entries.
*
* If the @metadata argument in the notifier is non-null, then it will
* point to the associated struct smux_meta_* structure.
*/
enum msm_smp2p_events {
SMP2P_OPEN, /* data is NULL */
SMP2P_ENTRY_UPDATE, /* data => struct msm_smp2p_update_notif */
};
/**
* Passed in response to a SMP2P_ENTRY_UPDATE event.
*
* @prev_value: previous value of entry
* @current_value: latest value of entry
*/
struct msm_smp2p_update_notif {
uint32_t previous_value;
uint32_t current_value;
};
int msm_smp2p_out_open(int remote_pid, const char *entry,
struct notifier_block *open_notifier,
struct msm_smp2p_out **handle);
int msm_smp2p_out_close(struct msm_smp2p_out **handle);
int msm_smp2p_out_read(struct msm_smp2p_out *handle, uint32_t *data);
int msm_smp2p_out_write(struct msm_smp2p_out *handle, uint32_t data);
int msm_smp2p_out_modify(struct msm_smp2p_out *handle, uint32_t set_mask,
uint32_t clear_mask);
int msm_smp2p_in_read(int remote_pid, const char *entry, uint32_t *data);
int msm_smp2p_in_register(int remote_pid, const char *entry,
struct notifier_block *in_notifier);
int msm_smp2p_in_unregister(int remote_pid, const char *entry,
struct notifier_block *in_notifier);
#endif /* _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_ */

View File

@ -0,0 +1,487 @@
/* arch/arm/mach-msm/smp2p_spinlock_test.c
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/debugfs.h>
#include <linux/ctype.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/remote_spinlock.h>
#include <mach/msm_smsm.h>
#include "smd_private.h"
#include "smp2p_private.h"
#include "smp2p_test_common.h"
#define REMOTE_SPIN_PID 1
#define RS_END_THIEF_PID_BIT 20
#define RS_END_THIEF_MASK 0x00f00000
/* Spinlock commands used for testing Apps<->RPM spinlocks. */
enum RPM_SPINLOCK_CMDS {
RPM_CMD_INVALID,
RPM_CMD_START,
RPM_CMD_LOCKED,
RPM_CMD_UNLOCKED,
RPM_CMD_END,
};
/* Shared structure for testing Apps<->RPM spinlocks. */
struct rpm_spinlock_test {
uint32_t apps_cmd;
uint32_t apps_lock_count;
uint32_t rpm_cmd;
uint32_t rpm_lock_count;
};
/**
* smp2p_ut_remote_spinlock_core - Verify remote spinlock.
*
* @s: Pointer to output file
* @remote_pid: Remote processor to test
* @use_trylock: Use trylock to prevent an Apps deadlock if the
* remote spinlock fails.
*/
static void smp2p_ut_remote_spinlock_core(struct seq_file *s, int remote_pid,
bool use_trylock)
{
int failed = 0;
unsigned lock_count = 0;
struct msm_smp2p_out *handle = NULL;
int ret;
uint32_t test_request;
uint32_t test_response;
struct mock_cb_data cb_out;
struct mock_cb_data cb_in;
unsigned long flags;
unsigned n;
unsigned test_num;
bool have_lock;
bool timeout;
int failed_tmp;
int spinlock_owner;
remote_spinlock_t *smem_spinlock;
seq_printf(s, "Running %s for '%s' remote pid %d\n",
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
cb_out.initialized = false;
cb_in.initialized = false;
mock_cb_data_init(&cb_out);
mock_cb_data_init(&cb_in);
do {
smem_spinlock = smem_get_remote_spinlock();
UT_ASSERT_PTR(smem_spinlock, !=, NULL);
/* Open output entry */
ret = msm_smp2p_out_open(remote_pid, SMP2P_RLPB_ENTRY_NAME,
&cb_out.nb, &handle);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_out.cb_completion, HZ * 2),
>, 0);
UT_ASSERT_INT(cb_out.cb_count, ==, 1);
UT_ASSERT_INT(cb_out.event_open, ==, 1);
/* Open inbound entry */
ret = msm_smp2p_in_register(remote_pid, SMP2P_RLPB_ENTRY_NAME,
&cb_in.nb);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ * 2),
>, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
UT_ASSERT_INT(cb_in.event_open, ==, 1);
/* Send start */
mock_cb_data_reset(&cb_in);
mock_cb_data_reset(&cb_out);
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_START);
SMP2P_SET_RMT_DATA(test_request, 0x0);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ * 2),
>, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
ret = msm_smp2p_in_read(remote_pid, SMP2P_RLPB_ENTRY_NAME,
&test_response);
UT_ASSERT_INT(ret, ==, 0);
test_response = SMP2P_GET_RMT_CMD(test_response);
if (test_response != SMP2P_LB_CMD_RSPIN_LOCKED &&
test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED) {
/* invalid response from remote - abort test */
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
SMP2P_SET_RMT_DATA(test_request, 0x0);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_LOCKED, ==,
test_response);
}
/* Run spinlock test */
if (use_trylock)
seq_printf(s, "\tUsing remote_spin_trylock\n");
else
seq_printf(s, "\tUsing remote_spin_lock\n");
flags = 0;
have_lock = false;
timeout = false;
spinlock_owner = 0;
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE_REQ(test_request);
for (test_num = 0; !failed && test_num < 10000; ++test_num) {
/* try to acquire spinlock */
if (use_trylock) {
unsigned long j_start = jiffies;
while (!remote_spin_trylock_irqsave(
smem_spinlock, flags)) {
if (jiffies_to_msecs(jiffies - j_start)
> 1000) {
seq_printf(s,
"\tFail: Timeout trying to get the lock\n");
timeout = true;
break;
}
}
if (timeout)
break;
} else {
remote_spin_lock_irqsave(smem_spinlock, flags);
}
have_lock = true;
++lock_count;
/* tell the remote side that we have the lock */
SMP2P_SET_RMT_DATA(test_request, lock_count);
SMP2P_SET_RMT_CMD(test_request,
SMP2P_LB_CMD_RSPIN_LOCKED);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_INT(ret, ==, 0);
/* verify the other side doesn't say it has the lock */
for (n = 0; n < 1000; ++n) {
spinlock_owner =
remote_spin_owner(smem_spinlock);
if (spinlock_owner != REMOTE_SPIN_PID) {
/* lock stolen by remote side */
seq_printf(s,
"\tFail: Remote side (%d) stole lock (pid %d)\n",
remote_pid, spinlock_owner);
failed = true;
break;
}
spinlock_owner = 0;
ret = msm_smp2p_in_read(remote_pid,
SMP2P_RLPB_ENTRY_NAME, &test_response);
UT_ASSERT_INT(ret, ==, 0);
test_response =
SMP2P_GET_RMT_CMD(test_response);
UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_UNLOCKED, ==,
test_response);
}
if (failed)
break;
/* tell remote side we are unlocked and release lock */
SMP2P_SET_RMT_CMD(test_request,
SMP2P_LB_CMD_RSPIN_UNLOCKED);
(void)msm_smp2p_out_write(handle, test_request);
have_lock = false;
remote_spin_unlock_irqrestore(smem_spinlock, flags);
}
if (have_lock)
remote_spin_unlock_irqrestore(smem_spinlock, flags);
/* End test */
mock_cb_data_reset(&cb_in);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
SMP2P_SET_RMT_DATA(test_request, lock_count |
(spinlock_owner << RS_END_THIEF_PID_BIT));
(void)msm_smp2p_out_write(handle, test_request);
failed_tmp = failed;
failed = false;
do {
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ * 2),
>, 0);
INIT_COMPLETION(cb_in.cb_completion);
ret = msm_smp2p_in_read(remote_pid,
SMP2P_RLPB_ENTRY_NAME, &test_response);
UT_ASSERT_INT(ret, ==, 0);
} while (!failed &&
SMP2P_GET_RMT_CMD(test_response) !=
SMP2P_LB_CMD_RSPIN_END);
if (failed)
break;
failed = failed_tmp;
test_response = SMP2P_GET_RMT_DATA(test_response);
seq_printf(s,
"\tLocked spinlock local %u times; remote %u times",
lock_count,
test_response & ((1 << RS_END_THIEF_PID_BIT) - 1)
);
if (test_response & RS_END_THIEF_MASK) {
seq_printf(s,
"Remote side reporting lock stolen by pid %d.\n",
SMP2P_GET_BITS(test_response,
RS_END_THIEF_MASK,
RS_END_THIEF_PID_BIT));
failed = 1;
}
seq_printf(s, "\n");
/* Cleanup */
ret = msm_smp2p_out_close(&handle);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_PTR(handle, ==, NULL);
ret = msm_smp2p_in_unregister(remote_pid,
SMP2P_RLPB_ENTRY_NAME, &cb_in.nb);
UT_ASSERT_INT(ret, ==, 0);
if (!failed && !timeout)
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
if (handle) {
/* send end command */
test_request = 0;
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END);
SMP2P_SET_RMT_DATA(test_request, lock_count);
(void)msm_smp2p_out_write(handle, test_request);
(void)msm_smp2p_out_close(&handle);
}
(void)msm_smp2p_in_unregister(remote_pid,
SMP2P_RLPB_ENTRY_NAME, &cb_in.nb);
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
}
}
/**
* smp2p_ut_remote_spinlock_pid - Verify remote spinlock for a processor.
*
* @s: Pointer to output file
* @pid: Processor to test
* @use_trylock: Use trylock to prevent an Apps deadlock if the
* remote spinlock fails.
*/
static void smp2p_ut_remote_spinlock_pid(struct seq_file *s, int pid,
bool use_trylock)
{
struct smp2p_interrupt_config *int_cfg;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg) {
seq_printf(s, "Remote processor config unavailable\n");
return;
}
if (pid >= SMP2P_NUM_PROCS || !int_cfg[pid].is_configured)
return;
msm_smp2p_deinit_rmt_lpb_proc(pid);
smp2p_ut_remote_spinlock_core(s, pid, use_trylock);
msm_smp2p_init_rmt_lpb_proc(pid);
}
/**
* smp2p_ut_remote_spinlock - Verify remote spinlock for all processors.
*
* @s: pointer to output file
*/
static void smp2p_ut_remote_spinlock(struct seq_file *s)
{
int pid;
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
smp2p_ut_remote_spinlock_pid(s, pid, false);
}
/**
* smp2p_ut_remote_spin_trylock - Verify remote trylock for all processors.
*
* @s: Pointer to output file
*/
static void smp2p_ut_remote_spin_trylock(struct seq_file *s)
{
int pid;
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
smp2p_ut_remote_spinlock_pid(s, pid, true);
}
/**
* smp2p_ut_remote_spinlock - Verify remote spinlock for all processors.
*
* @s: pointer to output file
*
* This test verifies inbound and outbound functionality for all
* configured remote processor.
*/
static void smp2p_ut_remote_spinlock_modem(struct seq_file *s)
{
smp2p_ut_remote_spinlock_pid(s, SMP2P_MODEM_PROC, false);
}
static void smp2p_ut_remote_spinlock_adsp(struct seq_file *s)
{
smp2p_ut_remote_spinlock_pid(s, SMP2P_AUDIO_PROC, false);
}
static void smp2p_ut_remote_spinlock_wcnss(struct seq_file *s)
{
smp2p_ut_remote_spinlock_pid(s, SMP2P_WIRELESS_PROC, false);
}
/**
* smp2p_ut_remote_spinlock_rpm - Verify remote spinlock.
*
* @s: pointer to output file
* @remote_pid: Remote processor to test
*/
static void smp2p_ut_remote_spinlock_rpm(struct seq_file *s)
{
int failed = 0;
unsigned long flags;
unsigned n;
unsigned test_num;
struct rpm_spinlock_test *data_ptr;
remote_spinlock_t *smem_spinlock;
bool have_lock;
seq_printf(s, "Running %s for Apps<->RPM Test\n",
__func__);
do {
smem_spinlock = smem_get_remote_spinlock();
UT_ASSERT_PTR(smem_spinlock, !=, NULL);
data_ptr = smem_alloc2(SMEM_ID_VENDOR0,
sizeof(struct rpm_spinlock_test));
UT_ASSERT_PTR(0, !=, data_ptr);
/* Send start */
writel_relaxed(0, &data_ptr->apps_lock_count);
writel_relaxed(RPM_CMD_START, &data_ptr->apps_cmd);
seq_printf(s, "\tWaiting for RPM to start test\n");
for (n = 0; n < 1000; ++n) {
if (readl_relaxed(&data_ptr->rpm_cmd) !=
RPM_CMD_INVALID)
break;
usleep(1000);
}
if (readl_relaxed(&data_ptr->rpm_cmd) == RPM_CMD_INVALID) {
/* timeout waiting for RPM */
writel_relaxed(RPM_CMD_INVALID, &data_ptr->apps_cmd);
UT_ASSERT_INT(RPM_CMD_LOCKED, !=, RPM_CMD_INVALID);
}
/* Run spinlock test */
flags = 0;
have_lock = false;
for (test_num = 0; !failed && test_num < 10000; ++test_num) {
/* acquire spinlock */
remote_spin_lock_irqsave(smem_spinlock, flags);
have_lock = true;
writel_relaxed(++data_ptr->apps_lock_count,
&data_ptr->apps_lock_count);
writel_relaxed(RPM_CMD_LOCKED, &data_ptr->apps_cmd);
/*
* Ensure that the remote side sees our lock has
* been acquired before we start polling their status.
*/
wmb();
/* verify the other side doesn't say it has the lock */
for (n = 0; n < 1000; ++n) {
UT_ASSERT_HEX(RPM_CMD_UNLOCKED, ==,
readl_relaxed(&data_ptr->rpm_cmd));
}
if (failed)
break;
/* release spinlock */
have_lock = false;
writel_relaxed(RPM_CMD_UNLOCKED, &data_ptr->apps_cmd);
/*
* Ensure that our status-update write was committed
* before we unlock the spinlock.
*/
wmb();
remote_spin_unlock_irqrestore(smem_spinlock, flags);
}
if (have_lock)
remote_spin_unlock_irqrestore(smem_spinlock, flags);
/* End test */
writel_relaxed(RPM_CMD_INVALID, &data_ptr->apps_cmd);
seq_printf(s,
"\tLocked spinlock local %u remote %u\n",
readl_relaxed(&data_ptr->apps_lock_count),
readl_relaxed(&data_ptr->rpm_lock_count));
if (!failed)
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
}
}
static int __init smp2p_debugfs_init(void)
{
/*
* Add Unit Test entries.
*
* The idea with unit tests is that you can run all of them
* from ADB shell by doing:
* adb shell
* cat ut*
*
* And if particular tests fail, you can then repeatedly run the
* failing tests as you debug and resolve the failing test.
*/
smp2p_debug_create("ut_remote_spinlock",
smp2p_ut_remote_spinlock);
smp2p_debug_create("ut_remote_spin_trylock",
smp2p_ut_remote_spin_trylock);
smp2p_debug_create("ut_remote_spinlock_modem",
smp2p_ut_remote_spinlock_modem);
smp2p_debug_create("ut_remote_spinlock_adsp",
smp2p_ut_remote_spinlock_adsp);
smp2p_debug_create("ut_remote_spinlock_wcnss",
smp2p_ut_remote_spinlock_wcnss);
smp2p_debug_create("ut_remote_spinlock_rpm",
smp2p_ut_remote_spinlock_rpm);
return 0;
}
module_init(smp2p_debugfs_init);

View File

@ -0,0 +1,913 @@
/* arch/arm/mach-msm/smp2p_test.c
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/debugfs.h>
#include <linux/ctype.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include "smp2p_private.h"
#include "smp2p_test_common.h"
/**
* smp2p_ut_local_basic - Basic sanity test using local loopback.
*
* @s: pointer to output file
*
* This test simulates a simple write and read
* when remote processor does not exist.
*/
static void smp2p_ut_local_basic(struct seq_file *s)
{
int failed = 0;
struct msm_smp2p_out *smp2p_obj;
struct msm_smp2p_remote_mock *rmp = NULL;
int ret;
uint32_t test_request;
uint32_t test_response = 0;
static struct mock_cb_data cb_data;
seq_printf(s, "Running %s\n", __func__);
mock_cb_data_init(&cb_data);
do {
/* initialize mock edge and start opening */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
rmp = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(rmp, !=, NULL);
rmp->rx_interrupt_count = 0;
memset(&rmp->remote_item, 0,
sizeof(struct smp2p_smem_item));
msm_smp2p_set_remote_mock_exists(false);
ret = msm_smp2p_out_open(SMP2P_REMOTE_MOCK_PROC, "smp2p",
&cb_data.nb, &smp2p_obj);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
UT_ASSERT_INT(cb_data.cb_count, ==, 0);
rmp->rx_interrupt_count = 0;
/* simulate response from remote side */
rmp->remote_item.header.magic = SMP2P_MAGIC;
SMP2P_SET_LOCAL_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_REMOTE_MOCK_PROC);
SMP2P_SET_REMOTE_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_APPS_PROC);
SMP2P_SET_VERSION(
rmp->remote_item.header.feature_version, 1);
SMP2P_SET_FEATURES(
rmp->remote_item.header.feature_version, 0);
SMP2P_SET_ENT_TOTAL(
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
SMP2P_SET_ENT_VALID(
rmp->remote_item.header.valid_total_ent, 0);
rmp->remote_item.header.reserved = 0x0;
msm_smp2p_set_remote_mock_exists(true);
rmp->tx_interrupt();
/* verify port was opened */
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_data.cb_completion, HZ / 2), >, 0);
UT_ASSERT_INT(cb_data.cb_count, ==, 1);
UT_ASSERT_INT(cb_data.event_open, ==, 1);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
/* do write (test outbound entries) */
rmp->rx_interrupt_count = 0;
test_request = 0xC0DE;
ret = msm_smp2p_out_write(smp2p_obj, test_request);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
/* do read (test inbound entries) */
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(test_request, ==, test_response);
ret = msm_smp2p_out_close(&smp2p_obj);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_PTR(smp2p_obj, ==, 0);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
(void)msm_smp2p_out_close(&smp2p_obj);
}
}
/**
* smp2p_ut_local_late_open - Verify post-negotiation opening.
*
* @s: pointer to output file
*
* Verify entry creation for opening entries after negotiation is complete.
*/
static void smp2p_ut_local_late_open(struct seq_file *s)
{
int failed = 0;
struct msm_smp2p_out *smp2p_obj;
struct msm_smp2p_remote_mock *rmp = NULL;
int ret;
uint32_t test_request;
uint32_t test_response = 0;
static struct mock_cb_data cb_data;
seq_printf(s, "Running %s\n", __func__);
mock_cb_data_init(&cb_data);
do {
/* initialize mock edge */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
rmp = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(rmp, !=, NULL);
rmp->rx_interrupt_count = 0;
memset(&rmp->remote_item, 0,
sizeof(struct smp2p_smem_item));
rmp->remote_item.header.magic = SMP2P_MAGIC;
SMP2P_SET_LOCAL_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_REMOTE_MOCK_PROC);
SMP2P_SET_REMOTE_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_APPS_PROC);
SMP2P_SET_VERSION(
rmp->remote_item.header.feature_version, 1);
SMP2P_SET_FEATURES(
rmp->remote_item.header.feature_version, 0);
SMP2P_SET_ENT_TOTAL(
rmp->remote_item.header.valid_total_ent,
SMP2P_MAX_ENTRY);
SMP2P_SET_ENT_VALID(
rmp->remote_item.header.valid_total_ent, 0);
rmp->remote_item.header.reserved = 0x0;
msm_smp2p_set_remote_mock_exists(true);
ret = msm_smp2p_out_open(SMP2P_REMOTE_MOCK_PROC, "smp2p",
&cb_data.nb, &smp2p_obj);
UT_ASSERT_INT(ret, ==, 0);
/* verify port was opened */
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_data.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_data.cb_count, ==, 1);
UT_ASSERT_INT(cb_data.event_open, ==, 1);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
/* do write (test outbound entries) */
rmp->rx_interrupt_count = 0;
test_request = 0xC0DE;
ret = msm_smp2p_out_write(smp2p_obj, test_request);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
/* do read (test inbound entries) */
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(test_request, ==, test_response);
ret = msm_smp2p_out_close(&smp2p_obj);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_PTR(smp2p_obj, ==, 0);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
(void)msm_smp2p_out_close(&smp2p_obj);
}
}
/**
* smp2p_ut_local_early_open - Verify pre-negotiation opening.
*
* @s: pointer to output file
*
* Verify entry creation for opening entries before negotiation is complete.
*/
static void smp2p_ut_local_early_open(struct seq_file *s)
{
int failed = 0;
struct msm_smp2p_out *smp2p_obj;
struct msm_smp2p_remote_mock *rmp = NULL;
struct smp2p_smem *outbound_item;
int negotiation_state;
int ret;
uint32_t test_request;
uint32_t test_response = 0;
static struct mock_cb_data cb_data;
seq_printf(s, "Running %s\n", __func__);
mock_cb_data_init(&cb_data);
do {
/* initialize mock edge, but don't enable, yet */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
rmp = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(rmp, !=, NULL);
rmp->rx_interrupt_count = 0;
memset(&rmp->remote_item, 0,
sizeof(struct smp2p_smem_item));
rmp->remote_item.header.magic = SMP2P_MAGIC;
SMP2P_SET_LOCAL_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_REMOTE_MOCK_PROC);
SMP2P_SET_REMOTE_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_APPS_PROC);
SMP2P_SET_VERSION(
rmp->remote_item.header.feature_version, 1);
SMP2P_SET_FEATURES(
rmp->remote_item.header.feature_version, 0);
SMP2P_SET_ENT_TOTAL(
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
SMP2P_SET_ENT_VALID(
rmp->remote_item.header.valid_total_ent, 0);
rmp->remote_item.header.reserved = 0x0;
msm_smp2p_set_remote_mock_exists(false);
UT_ASSERT_PTR(NULL, ==,
smp2p_get_in_item(SMP2P_REMOTE_MOCK_PROC));
/* initiate open, but verify it doesn't complete */
ret = msm_smp2p_out_open(SMP2P_REMOTE_MOCK_PROC, "smp2p",
&cb_data.nb, &smp2p_obj);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_data.cb_completion, HZ / 8),
==, 0);
UT_ASSERT_INT(cb_data.cb_count, ==, 0);
UT_ASSERT_INT(cb_data.event_open, ==, 0);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
outbound_item = smp2p_get_out_item(SMP2P_REMOTE_MOCK_PROC,
&negotiation_state);
UT_ASSERT_PTR(outbound_item, !=, NULL);
UT_ASSERT_INT(negotiation_state, ==, SMP2P_EDGE_STATE_OPENING);
UT_ASSERT_INT(0, ==,
SMP2P_GET_ENT_VALID(outbound_item->valid_total_ent));
/* verify that read/write don't work yet */
rmp->rx_interrupt_count = 0;
test_request = 0x0;
ret = msm_smp2p_out_write(smp2p_obj, test_request);
UT_ASSERT_INT(ret, ==, -ENODEV);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 0);
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
UT_ASSERT_INT(ret, ==, -ENODEV);
/* allocate remote entry and verify open */
msm_smp2p_set_remote_mock_exists(true);
rmp->tx_interrupt();
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_data.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_data.cb_count, ==, 1);
UT_ASSERT_INT(cb_data.event_open, ==, 1);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
/* do write (test outbound entries) */
rmp->rx_interrupt_count = 0;
test_request = 0xC0DE;
ret = msm_smp2p_out_write(smp2p_obj, test_request);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
/* do read (test inbound entries) */
ret = msm_smp2p_out_read(smp2p_obj, &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(test_request, ==, test_response);
ret = msm_smp2p_out_close(&smp2p_obj);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_PTR(smp2p_obj, ==, 0);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
(void)msm_smp2p_out_close(&smp2p_obj);
}
}
/**
* smp2p_ut_mock_loopback - Exercise the remote loopback using remote mock.
*
* @s: pointer to output file
*
* This test exercises the remote loopback code using
* remote mock object. The remote mock object simulates the remote
* processor sending remote loopback commands to the local processor.
*/
static void smp2p_ut_mock_loopback(struct seq_file *s)
{
int failed = 0;
struct msm_smp2p_remote_mock *rmp = NULL;
int ret;
uint32_t test_request = 0;
uint32_t test_response = 0;
struct msm_smp2p_out *local;
seq_printf(s, "Running %s\n", __func__);
do {
/* Initialize the mock edge */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
rmp = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(rmp, !=, NULL);
memset(&rmp->remote_item, 0,
sizeof(struct smp2p_smem_item));
rmp->remote_item.header.magic = SMP2P_MAGIC;
SMP2P_SET_LOCAL_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_REMOTE_MOCK_PROC);
SMP2P_SET_REMOTE_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_APPS_PROC);
SMP2P_SET_VERSION(
rmp->remote_item.header.feature_version, 1);
SMP2P_SET_FEATURES(
rmp->remote_item.header.feature_version, 0);
SMP2P_SET_ENT_TOTAL(
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
SMP2P_SET_ENT_VALID(
rmp->remote_item.header.valid_total_ent, 1);
rmp->remote_item.header.reserved = 0x0;
msm_smp2p_set_remote_mock_exists(true);
/* Create test entry and attach loopback server */
rmp->rx_interrupt_count = 0;
INIT_COMPLETION(rmp->cb_completion);
strlcpy(rmp->remote_item.entries[0].name, "smp2p",
SMP2P_MAX_ENTRY_NAME);
rmp->remote_item.entries[0].entry = 0;
rmp->tx_interrupt();
local = msm_smp2p_init_rmt_lpb_proc(SMP2P_REMOTE_MOCK_PROC);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&rmp->cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 2);
/* Send Echo Command */
rmp->rx_interrupt_count = 0;
INIT_COMPLETION(rmp->cb_completion);
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_ECHO);
SMP2P_SET_RMT_DATA(test_request, 10);
rmp->remote_item.entries[0].entry = test_request;
rmp->tx_interrupt();
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&rmp->cb_completion, HZ / 2),
>, 0);
/* Verify Echo Response */
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
ret = msm_smp2p_out_read(local,
&test_response);
UT_ASSERT_INT(ret, ==, 0);
test_response = SMP2P_GET_RMT_DATA(test_response);
UT_ASSERT_INT(test_response, ==, 10);
/* Send PINGPONG command */
test_request = 0;
test_response = 0;
rmp->rx_interrupt_count = 0;
INIT_COMPLETION(rmp->cb_completion);
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_PINGPONG);
SMP2P_SET_RMT_DATA(test_request, 10);
rmp->remote_item.entries[0].entry = test_request;
rmp->tx_interrupt();
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&rmp->cb_completion, HZ / 2),
>, 0);
/* Verify PINGPONG Response */
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
ret = msm_smp2p_out_read(local, &test_response);
UT_ASSERT_INT(ret, ==, 0);
test_response = SMP2P_GET_RMT_DATA(test_response);
UT_ASSERT_INT(test_response, ==, 9);
/* Send CLEARALL command */
test_request = 0;
test_response = 0;
rmp->rx_interrupt_count = 0;
INIT_COMPLETION(rmp->cb_completion);
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_CLEARALL);
SMP2P_SET_RMT_DATA(test_request, 10);
rmp->remote_item.entries[0].entry = test_request;
rmp->tx_interrupt();
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&rmp->cb_completion, HZ / 2),
>, 0);
/* Verify CLEARALL response */
UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1);
ret = msm_smp2p_out_read(local, &test_response);
UT_ASSERT_INT(ret, ==, 0);
test_response = SMP2P_GET_RMT_DATA(test_response);
UT_ASSERT_INT(test_response, ==, 0);
ret = msm_smp2p_deinit_rmt_lpb_proc(SMP2P_REMOTE_MOCK_PROC);
UT_ASSERT_INT(ret, ==, 0);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
msm_smp2p_deinit_rmt_lpb_proc(SMP2P_REMOTE_MOCK_PROC);
}
}
/**
* smp2p_ut_remote_inout_core - Verify inbound/outbound functionality.
*
* @s: pointer to output file
* @remote_pid: Remote processor to test
*
* This test verifies inbound/outbound functionality for the remote processor.
*/
static void smp2p_ut_remote_inout_core(struct seq_file *s, int remote_pid)
{
int failed = 0;
struct msm_smp2p_out *handle;
int ret;
uint32_t test_request;
uint32_t test_response = 0;
static struct mock_cb_data cb_out;
static struct mock_cb_data cb_in;
seq_printf(s, "Running %s for '%s' remote pid %d\n",
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
mock_cb_data_init(&cb_out);
mock_cb_data_init(&cb_in);
do {
/* Open output entry */
ret = msm_smp2p_out_open(remote_pid, "smp2p",
&cb_out.nb, &handle);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_out.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_out.cb_count, ==, 1);
UT_ASSERT_INT(cb_out.event_open, ==, 1);
/* Open inbound entry */
ret = msm_smp2p_in_register(remote_pid, "smp2p",
&cb_in.nb);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
UT_ASSERT_INT(cb_in.event_open, ==, 1);
/* Write an echo request */
mock_cb_data_reset(&cb_out);
mock_cb_data_reset(&cb_in);
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_ECHO);
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_INT(ret, ==, 0);
/* Verify inbound reply */
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
UT_ASSERT_INT(SMP2P_GET_RMT_DATA(
cb_in.entry_data.current_value), ==, 0xAA55);
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(0, ==, SMP2P_GET_RMT_CMD_TYPE(test_response));
UT_ASSERT_INT(SMP2P_LB_CMD_ECHO, ==,
SMP2P_GET_RMT_CMD(test_response));
UT_ASSERT_INT(0xAA55, ==, SMP2P_GET_RMT_DATA(test_response));
/* Write a clear all request */
mock_cb_data_reset(&cb_in);
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_CLEARALL);
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_INT(ret, ==, 0);
/* Verify inbound reply */
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
UT_ASSERT_INT(SMP2P_GET_RMT_DATA(
cb_in.entry_data.current_value), ==, 0x0000);
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(0, ==, SMP2P_GET_RMT_CMD_TYPE(test_response));
UT_ASSERT_INT(0x0000, ==, SMP2P_GET_RMT_DATA(test_response));
/* Write a decrement request */
mock_cb_data_reset(&cb_in);
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_PINGPONG);
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_INT(ret, ==, 0);
/* Verify inbound reply */
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ / 2),
>, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 1);
UT_ASSERT_INT(cb_in.event_entry_update, ==, 1);
UT_ASSERT_INT(SMP2P_GET_RMT_DATA(
cb_in.entry_data.current_value), ==, 0xAA54);
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(0, ==, SMP2P_GET_RMT_CMD_TYPE(test_response));
UT_ASSERT_INT(SMP2P_LB_CMD_PINGPONG, ==,
SMP2P_GET_RMT_CMD(test_response));
UT_ASSERT_INT(0xAA54, ==, SMP2P_GET_RMT_DATA(test_response));
/* Test the ignore flag */
mock_cb_data_reset(&cb_in);
test_request = 0x0;
SMP2P_SET_RMT_CMD_TYPE(test_request, 1);
SMP2P_SET_RMT_CMD(test_request, SMP2P_RLPB_IGNORE);
SMP2P_SET_RMT_DATA(test_request, 0xAA55);
ret = msm_smp2p_out_write(handle, test_request);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&cb_in.cb_completion, HZ / 2),
==, 0);
UT_ASSERT_INT(cb_in.cb_count, ==, 0);
UT_ASSERT_INT(cb_in.event_entry_update, ==, 0);
ret = msm_smp2p_in_read(remote_pid, "smp2p", &test_response);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(0xAA54, ==, SMP2P_GET_RMT_DATA(test_response));
/* Cleanup */
ret = msm_smp2p_out_close(&handle);
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_PTR(handle, ==, 0);
ret = msm_smp2p_in_unregister(remote_pid, "smp2p", &cb_in.nb);
UT_ASSERT_INT(ret, ==, 0);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
if (handle)
(void)msm_smp2p_out_close(&handle);
(void)msm_smp2p_in_unregister(remote_pid, "smp2p", &cb_in.nb);
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
}
}
/**
* smp2p_ut_remote_inout - Verify inbound/outbound functionality for all.
*
* @s: pointer to output file
*
* This test verifies inbound and outbound functionality for all
* configured remote processor.
*/
static void smp2p_ut_remote_inout(struct seq_file *s)
{
struct smp2p_interrupt_config *int_cfg;
int pid;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg) {
seq_printf(s,
"Remote processor config unavailable\n");
return;
}
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
if (!int_cfg[pid].is_configured)
continue;
msm_smp2p_deinit_rmt_lpb_proc(pid);
smp2p_ut_remote_inout_core(s, pid);
msm_smp2p_init_rmt_lpb_proc(pid);
}
}
/**
* smp2p_ut_remote_out_max_entries_core - Verify open functionality.
*
* @s: pointer to output file
* @remote_pid: Remote processor for which the test is executed.
*
* This test verifies open functionality by creating maximum outbound entries.
*/
static void smp2p_ut_remote_out_max_entries_core(struct seq_file *s,
int remote_pid)
{
int j = 0;
int failed = 0;
struct msm_smp2p_out *handle[SMP2P_MAX_ENTRY];
int ret;
static struct mock_cb_data cb_out[SMP2P_MAX_ENTRY];
char entry_name[SMP2P_MAX_ENTRY_NAME];
int num_created;
seq_printf(s, "Running %s for '%s' remote pid %d\n",
__func__, smp2p_pid_to_name(remote_pid), remote_pid);
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
handle[j] = NULL;
mock_cb_data_init(&cb_out[j]);
}
do {
num_created = 0;
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
/* Open as many output entries as possible */
scnprintf((char *)entry_name, SMP2P_MAX_ENTRY_NAME,
"smp2p%d", j);
ret = msm_smp2p_out_open(remote_pid, entry_name,
&cb_out[j].nb, &handle[j]);
if (ret == -ENOMEM)
/* hit max number */
break;
UT_ASSERT_INT(ret, ==, 0);
++num_created;
}
if (failed)
break;
/* verify we created more than 1 entry */
UT_ASSERT_INT(num_created, <=, SMP2P_MAX_ENTRY);
UT_ASSERT_INT(num_created, >, 0);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
}
/* cleanup */
for (j = 0; j < SMP2P_MAX_ENTRY; j++)
ret = msm_smp2p_out_close(&handle[j]);
}
/**
* smp2p_ut_remote_out_max_entries - Verify open for all configured processors.
*
* @s: pointer to output file
*
* This test verifies creating max number of entries for
* all configured remote processor.
*/
static void smp2p_ut_remote_out_max_entries(struct seq_file *s)
{
struct smp2p_interrupt_config *int_cfg;
int pid;
int_cfg = smp2p_get_interrupt_config();
if (!int_cfg) {
seq_printf(s,
"Remote processor config unavailable\n");
return;
}
for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
if (!int_cfg[pid].is_configured)
continue;
smp2p_ut_remote_out_max_entries_core(s, pid);
}
}
/**
* smp2p_ut_local_in_max_entries - Verify registering and unregistering.
*
* @s: pointer to output file
*
* This test verifies registering and unregistering for inbound entries using
* the remote mock processor.
*/
static void smp2p_ut_local_in_max_entries(struct seq_file *s)
{
int j = 0;
int failed = 0;
struct msm_smp2p_remote_mock *rmp = NULL;
int ret;
static struct mock_cb_data cb_in[SMP2P_MAX_ENTRY];
static struct mock_cb_data cb_out;
seq_printf(s, "Running %s\n", __func__);
for (j = 0; j < SMP2P_MAX_ENTRY; j++)
mock_cb_data_init(&cb_in[j]);
mock_cb_data_init(&cb_out);
do {
/* Initialize mock edge */
ret = smp2p_reset_mock_edge();
UT_ASSERT_INT(ret, ==, 0);
rmp = msm_smp2p_get_remote_mock();
UT_ASSERT_PTR(rmp, !=, NULL);
rmp->rx_interrupt_count = 0;
memset(&rmp->remote_item, 0,
sizeof(struct smp2p_smem_item));
rmp->remote_item.header.magic = SMP2P_MAGIC;
SMP2P_SET_LOCAL_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_REMOTE_MOCK_PROC);
SMP2P_SET_REMOTE_PID(
rmp->remote_item.header.rem_loc_proc_id,
SMP2P_APPS_PROC);
SMP2P_SET_VERSION(
rmp->remote_item.header.feature_version, 1);
SMP2P_SET_FEATURES(
rmp->remote_item.header.feature_version, 0);
SMP2P_SET_ENT_TOTAL(
rmp->remote_item.header.valid_total_ent, SMP2P_MAX_ENTRY);
SMP2P_SET_ENT_VALID(
rmp->remote_item.header.valid_total_ent, 0);
rmp->remote_item.header.reserved = 0x0;
msm_smp2p_set_remote_mock_exists(true);
/* Create Max Entries in the remote mock object */
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
scnprintf(rmp->remote_item.entries[j].name,
SMP2P_MAX_ENTRY_NAME, "smp2p%d", j);
rmp->remote_item.entries[j].entry = 0;
rmp->tx_interrupt();
}
/* Register for in entries */
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
ret = msm_smp2p_in_register(SMP2P_REMOTE_MOCK_PROC,
rmp->remote_item.entries[j].name,
&(cb_in[j].nb));
UT_ASSERT_INT(ret, ==, 0);
UT_ASSERT_INT(
(int)wait_for_completion_timeout(
&(cb_in[j].cb_completion), HZ / 2),
>, 0);
UT_ASSERT_INT(cb_in[j].cb_count, ==, 1);
UT_ASSERT_INT(cb_in[j].event_entry_update, ==, 0);
}
UT_ASSERT_INT(j, ==, SMP2P_MAX_ENTRY);
/* Unregister */
for (j = 0; j < SMP2P_MAX_ENTRY; j++) {
ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC,
rmp->remote_item.entries[j].name,
&(cb_in[j].nb));
UT_ASSERT_INT(ret, ==, 0);
}
UT_ASSERT_INT(j, ==, SMP2P_MAX_ENTRY);
seq_printf(s, "\tOK\n");
} while (0);
if (failed) {
pr_err("%s: Failed\n", __func__);
seq_printf(s, "\tFailed\n");
for (j = 0; j < SMP2P_MAX_ENTRY; j++)
ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC,
rmp->remote_item.entries[j].name,
&(cb_in[j].nb));
}
}
static struct dentry *dent;
static int debugfs_show(struct seq_file *s, void *data)
{
void (*show)(struct seq_file *) = s->private;
show(s);
return 0;
}
static int debug_open(struct inode *inode, struct file *file)
{
return single_open(file, debugfs_show, inode->i_private);
}
static const struct file_operations debug_ops = {
.open = debug_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
};
void smp2p_debug_create(const char *name,
void (*show)(struct seq_file *))
{
struct dentry *file;
file = debugfs_create_file(name, 0444, dent, show, &debug_ops);
if (!file)
pr_err("%s: unable to create file '%s'\n", __func__, name);
}
static int __init smp2p_debugfs_init(void)
{
dent = debugfs_create_dir("smp2p_test", 0);
if (IS_ERR(dent))
return PTR_ERR(dent);
/*
* Add Unit Test entries.
*
* The idea with unit tests is that you can run all of them
* from ADB shell by doing:
* adb shell
* cat ut*
*
* And if particular tests fail, you can then repeatedly run the
* failing tests as you debug and resolve the failing test.
*/
smp2p_debug_create("ut_local_basic",
smp2p_ut_local_basic);
smp2p_debug_create("ut_local_late_open",
smp2p_ut_local_late_open);
smp2p_debug_create("ut_local_early_open",
smp2p_ut_local_early_open);
smp2p_debug_create("ut_mock_loopback",
smp2p_ut_mock_loopback);
smp2p_debug_create("ut_remote_inout",
smp2p_ut_remote_inout);
smp2p_debug_create("ut_local_in_max_entries",
smp2p_ut_local_in_max_entries);
smp2p_debug_create("ut_remote_out_max_entries",
smp2p_ut_remote_out_max_entries);
return 0;
}
module_init(smp2p_debugfs_init);

View File

@ -0,0 +1,217 @@
/* arch/arm/mach-msm/smp2p_test_common.h
*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _ARCH_ARM_MACH_MSM_SMP2P_TEST_COMMON_H_
#define _ARCH_ARM_MACH_MSM_SMP2P_TEST_COMMON_H_
#include <linux/debugfs.h>
/**
* Unit test assertion for logging test cases.
*
* @a lval
* @b rval
* @cmp comparison operator
*
* Assertion fails if (@a cmp @b) is not true which then
* logs the function and line number where the error occurred
* along with the values of @a and @b.
*
* Assumes that the following local variables exist:
* @s - sequential output file pointer
* @failed - set to true if test fails
*/
#define UT_ASSERT_INT(a, cmp, b) \
{ \
int a_tmp = (a); \
int b_tmp = (b); \
if (!((a_tmp)cmp(b_tmp))) { \
seq_printf(s, \
"%s:%d Fail: " #a "(%d) " #cmp " " #b "(%d)\n", \
__func__, __LINE__, \
a_tmp, b_tmp); \
failed = 1; \
break; \
} \
}
#define UT_ASSERT_PTR(a, cmp, b) \
{ \
void *a_tmp = (a); \
void *b_tmp = (b); \
if (!((a_tmp)cmp(b_tmp))) { \
seq_printf(s, \
"%s:%d Fail: " #a "(%p) " #cmp " " #b "(%p)\n", \
__func__, __LINE__, \
a_tmp, b_tmp); \
failed = 1; \
break; \
} \
}
#define UT_ASSERT_UINT(a, cmp, b) \
{ \
unsigned a_tmp = (a); \
unsigned b_tmp = (b); \
if (!((a_tmp)cmp(b_tmp))) { \
seq_printf(s, \
"%s:%d Fail: " #a "(%u) " #cmp " " #b "(%u)\n", \
__func__, __LINE__, \
a_tmp, b_tmp); \
failed = 1; \
break; \
} \
}
#define UT_ASSERT_HEX(a, cmp, b) \
{ \
unsigned a_tmp = (a); \
unsigned b_tmp = (b); \
if (!((a_tmp)cmp(b_tmp))) { \
seq_printf(s, \
"%s:%d Fail: " #a "(%x) " #cmp " " #b "(%x)\n", \
__func__, __LINE__, \
a_tmp, b_tmp); \
failed = 1; \
break; \
} \
}
/**
* In-range unit test assertion for test cases.
*
* @a lval
* @minv Minimum value
* @maxv Maximum value
*
* Assertion fails if @a is not on the exclusive range minv, maxv
* ((@a < @minv) or (@a > @maxv)). In the failure case, the macro
* logs the function and line number where the error occurred along
* with the values of @a and @minv, @maxv.
*
* Assumes that the following local variables exist:
* @s - sequential output file pointer
* @failed - set to true if test fails
*/
#define UT_ASSERT_INT_IN_RANGE(a, minv, maxv) \
{ \
int a_tmp = (a); \
int minv_tmp = (minv); \
int maxv_tmp = (maxv); \
if (((a_tmp) < (minv_tmp)) || ((a_tmp) > (maxv_tmp))) { \
seq_printf(s, \
"%s:%d Fail: " #a "(%d) < " #minv "(%d) or " \
#a "(%d) > " #maxv "(%d)\n", \
__func__, __LINE__, \
a_tmp, minv_tmp, a_tmp, maxv_tmp); \
failed = 1; \
break; \
} \
}
/* Structure to track state changes for the notifier callback. */
struct mock_cb_data {
bool initialized;
spinlock_t lock;
struct notifier_block nb;
/* events */
struct completion cb_completion;
int cb_count;
int event_open;
int event_entry_update;
struct msm_smp2p_update_notif entry_data;
};
void smp2p_debug_create(const char *name, void (*show)(struct seq_file *));
static inline int smp2p_test_notify(struct notifier_block *self,
unsigned long event, void *data);
/**
* Reset mock callback data to default values.
*
* @cb: Mock callback data
*/
static inline void mock_cb_data_reset(struct mock_cb_data *cb)
{
INIT_COMPLETION(cb->cb_completion);
cb->cb_count = 0;
cb->event_open = 0;
cb->event_entry_update = 0;
memset(&cb->entry_data, 0,
sizeof(struct msm_smp2p_update_notif));
}
/**
* Initialize mock callback data.
*
* @cb: Mock callback data
*/
static inline void mock_cb_data_init(struct mock_cb_data *cb)
{
if (!cb->initialized) {
init_completion(&cb->cb_completion);
spin_lock_init(&cb->lock);
cb->initialized = true;
cb->nb.notifier_call = smp2p_test_notify;
memset(&cb->entry_data, 0,
sizeof(struct msm_smp2p_update_notif));
}
mock_cb_data_reset(cb);
}
/**
* Notifier function passed into SMP2P for testing.
*
* @self: Pointer to calling notifier block
* @event: Event
* @data: Event-specific data
* @returns: 0
*/
static inline int smp2p_test_notify(struct notifier_block *self,
unsigned long event, void *data)
{
struct mock_cb_data *cb_data_ptr;
unsigned long flags;
cb_data_ptr = container_of(self, struct mock_cb_data, nb);
spin_lock_irqsave(&cb_data_ptr->lock, flags);
switch (event) {
case SMP2P_OPEN:
++cb_data_ptr->event_open;
if (data) {
cb_data_ptr->entry_data =
*(struct msm_smp2p_update_notif *)(data);
}
break;
case SMP2P_ENTRY_UPDATE:
++cb_data_ptr->event_entry_update;
if (data) {
cb_data_ptr->entry_data =
*(struct msm_smp2p_update_notif *)(data);
}
break;
default:
pr_err("%s Unknown event\n", __func__);
break;
}
++cb_data_ptr->cb_count;
complete(&cb_data_ptr->cb_completion);
spin_unlock_irqrestore(&cb_data_ptr->lock, flags);
return 0;
}
#endif /* _ARCH_ARM_MACH_MSM_SMP2P_TEST_COMMON_H_ */