diff options
author | Karthikeyan Ramasubramanian <kramasub@codeaurora.org> | 2016-01-22 16:27:03 -0700 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:02:10 -0700 |
commit | d4516a3652da25020efa5aad5a9eee0cf96a74e4 (patch) | |
tree | 68035093d2747170de7617519a3b57cc9c0a9ac2 /drivers/soc | |
parent | 180a1bcbe0a7516286ceecba6adb8e548ac366bc (diff) |
soc: qcom: Add snapshot of SMP2P Driver
This snapshot is taken as of msm-3.18 commit e70ad0cd (Promotion of
kernel.lnx.3.18-151201.)
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-rw-r--r-- | drivers/soc/qcom/Kconfig | 21 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 2 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p.c | 1951 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_debug.c | 335 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_loopback.c | 449 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_private.h | 253 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_private_api.h | 80 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_sleepstate.c | 104 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_spinlock_test.c | 804 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_test.c | 1322 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p_test_common.h | 213 |
11 files changed, 5534 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index b022f07e4165..12ce797383e7 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -70,6 +70,27 @@ config MSM_GLINK_SMEM_NATIVE_XPRT transport to only connecting with entities internal to the System-on-Chip. +config MSM_SMP2P + bool "SMSM Point-to-Point (SMP2P)" + depends on MSM_SMEM + help + Provide point-to-point remote signaling support. + SMP2P enables transferring 32-bit values between + the local and a remote system using shared + memory and interrupts. A client can open multiple + 32-bit values by specifying a unique string and + remote processor ID. + +config MSM_SMP2P_TEST + bool "SMSM Point-to-Point Test" + depends on MSM_SMP2P + help + Enables loopback and unit testing support for + SMP2P. Loopback support is used by other + processors to do unit testing. Unit tests + are used to verify the local and remote + implementations. + config MSM_RPM_SMD bool "RPM driver using SMD protocol" help diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 4225eb00adb0..1fbe488dcc70 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -5,6 +5,8 @@ obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o obj-$(CONFIG_MSM_GLINK_SMD_XPRT) += glink_smd_xprt.o obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT)+= glink_smem_native_xprt.o obj-$(CONFIG_ARCH_QCOM) += kryo-l2-accessors.o +obj-$(CONFIG_MSM_SMP2P) += smp2p.o smp2p_debug.o smp2p_sleepstate.o +obj-$(CONFIG_MSM_SMP2P_TEST) += smp2p_loopback.o smp2p_test.o smp2p_spinlock_test.o obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd.o ifdef CONFIG_DEBUG_FS obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o diff --git a/drivers/soc/qcom/smp2p.c b/drivers/soc/qcom/smp2p.c new file mode 100644 index 000000000000..fc5688b4bc8c --- /dev/null +++ b/drivers/soc/qcom/smp2p.c @@ -0,0 +1,1951 @@ +/* drivers/soc/qcom/smp2p.c + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/ipc_logging.h> +#include <linux/err.h> +#include <soc/qcom/smem.h> +#include "smp2p_private_api.h" +#include "smp2p_private.h" + +#define NUM_LOG_PAGES 3 + +/** + * struct msm_smp2p_out - This structure represents the outbound SMP2P entry. + * + * @remote_pid: Outbound processor ID. + * @name: Entry name. + * @out_edge_list: Adds this structure into smp2p_out_list_item::list. + * @msm_smp2p_notifier_list: Notifier block head used to notify for open event. + * @open_nb: Notifier block used to notify for open event. + * @l_smp2p_entry: Pointer to the actual entry in the SMEM item. + */ +struct msm_smp2p_out { + int remote_pid; + char name[SMP2P_MAX_ENTRY_NAME]; + struct list_head out_edge_list; + struct raw_notifier_head msm_smp2p_notifier_list; + struct notifier_block *open_nb; + uint32_t __iomem *l_smp2p_entry; +}; + +/** + * struct smp2p_out_list_item - Maintains the state of outbound edge. + * + * @out_item_lock_lha1: Lock protecting all elements of the structure. + * @list: list of outbound entries (struct msm_smp2p_out). + * @smem_edge_out: Pointer to outbound smem item. + * @smem_edge_state: State of the outbound edge. + * @ops_ptr: Pointer to internal version-specific SMEM item access functions. + * + * @feature_ssr_ack_enabled: SSR ACK Support Enabled + * @restart_ack: Current cached state of the local ack bit + */ +struct smp2p_out_list_item { + spinlock_t out_item_lock_lha1; + + struct list_head list; + struct smp2p_smem __iomem *smem_edge_out; + enum msm_smp2p_edge_state smem_edge_state; + struct smp2p_version_if *ops_ptr; + + bool feature_ssr_ack_enabled; + bool restart_ack; +}; +static struct smp2p_out_list_item out_list[SMP2P_NUM_PROCS]; + +static void *log_ctx; +static int smp2p_debug_mask = MSM_SMP2P_INFO | MSM_SMP2P_DEBUG; +module_param_named(debug_mask, smp2p_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +/** + * struct smp2p_in - Represents the entry on remote processor. + * + * @name: Name of the entry. + * @remote_pid: Outbound processor ID. + * @in_edge_list: Adds this structure into smp2p_in_list_item::list. + * @in_notifier_list: List for notifier block for entry opening/updates. + * @prev_entry_val: Previous value of the entry. + * @entry_ptr: Points to the current value in smem item. + * @notifier_count: Counts the number of notifier registered per pid,entry. + */ +struct smp2p_in { + int remote_pid; + char name[SMP2P_MAX_ENTRY_NAME]; + struct list_head in_edge_list; + struct raw_notifier_head in_notifier_list; + uint32_t prev_entry_val; + uint32_t __iomem *entry_ptr; + uint32_t notifier_count; +}; + +/** + * struct smp2p_in_list_item - Maintains the inbound edge state. + * + * @in_item_lock_lhb1: Lock protecting all elements of the structure. + * @list: List head for the entries on remote processor. + * @smem_edge_in: Pointer to the remote smem item. + */ +struct smp2p_in_list_item { + spinlock_t in_item_lock_lhb1; + struct list_head list; + struct smp2p_smem __iomem *smem_edge_in; + uint32_t item_size; + uint32_t safe_total_entries; +}; +static struct smp2p_in_list_item in_list[SMP2P_NUM_PROCS]; + +/** + * SMEM Item access function interface. + * + * This interface is used to help isolate the implementation of + * the functionality from any changes in the shared data structures + * that may happen as versions are changed. + * + * @is_supported: True if this version is supported by SMP2P + * @negotiate_features: Returns (sub)set of supported features + * @negotiation_complete: Called when negotiation has been completed + * @find_entry: Finds existing / next empty entry + * @create_entry: Creates a new entry + * @read_entry: Reads the value of an entry + * @write_entry: Writes a new value to an entry + * @modify_entry: Does a read/modify/write of an entry + * validate_size: Verifies the size of the remote SMEM item to ensure that + * an invalid item size doesn't result in an out-of-bounds + * memory access. + */ +struct smp2p_version_if { + /* common functions */ + bool is_supported; + uint32_t (*negotiate_features)(uint32_t features); + void (*negotiation_complete)(struct smp2p_out_list_item *); + void (*find_entry)(struct smp2p_smem __iomem *item, + uint32_t entries_total, char *name, + uint32_t **entry_ptr, int *empty_spot); + + /* outbound entry functions */ + int (*create_entry)(struct msm_smp2p_out *); + int (*read_entry)(struct msm_smp2p_out *, uint32_t *); + int (*write_entry)(struct msm_smp2p_out *, uint32_t); + int (*modify_entry)(struct msm_smp2p_out *, uint32_t, uint32_t, bool); + + /* inbound entry functions */ + struct smp2p_smem __iomem *(*validate_size)(int remote_pid, + struct smp2p_smem __iomem *, uint32_t); +}; + +static int smp2p_do_negotiation(int remote_pid, struct smp2p_out_list_item *p); +static void smp2p_send_interrupt(int remote_pid); + +/* v0 (uninitialized SMEM item) interface functions */ +static uint32_t smp2p_negotiate_features_v0(uint32_t features); +static void smp2p_negotiation_complete_v0(struct smp2p_out_list_item *out_item); +static void smp2p_find_entry_v0(struct smp2p_smem __iomem *item, + uint32_t entries_total, char *name, uint32_t **entry_ptr, + int *empty_spot); +static int smp2p_out_create_v0(struct msm_smp2p_out *); +static int smp2p_out_read_v0(struct msm_smp2p_out *, uint32_t *); +static int smp2p_out_write_v0(struct msm_smp2p_out *, uint32_t); +static int smp2p_out_modify_v0(struct msm_smp2p_out *, + uint32_t, uint32_t, bool); +static struct smp2p_smem __iomem *smp2p_in_validate_size_v0(int remote_pid, + struct smp2p_smem __iomem *smem_item, uint32_t size); + +/* v1 interface functions */ +static uint32_t smp2p_negotiate_features_v1(uint32_t features); +static void smp2p_negotiation_complete_v1(struct smp2p_out_list_item *out_item); +static void smp2p_find_entry_v1(struct smp2p_smem __iomem *item, + uint32_t entries_total, char *name, uint32_t **entry_ptr, + int *empty_spot); +static int smp2p_out_create_v1(struct msm_smp2p_out *); +static int smp2p_out_read_v1(struct msm_smp2p_out *, uint32_t *); +static int smp2p_out_write_v1(struct msm_smp2p_out *, uint32_t); +static int smp2p_out_modify_v1(struct msm_smp2p_out *, + uint32_t, uint32_t, bool); +static struct smp2p_smem __iomem *smp2p_in_validate_size_v1(int remote_pid, + struct smp2p_smem __iomem *smem_item, uint32_t size); + +/* Version interface functions */ +static struct smp2p_version_if version_if[] = { + [0] = { + .negotiate_features = smp2p_negotiate_features_v0, + .negotiation_complete = smp2p_negotiation_complete_v0, + .find_entry = smp2p_find_entry_v0, + .create_entry = smp2p_out_create_v0, + .read_entry = smp2p_out_read_v0, + .write_entry = smp2p_out_write_v0, + .modify_entry = smp2p_out_modify_v0, + .validate_size = smp2p_in_validate_size_v0, + }, + [1] = { + .is_supported = true, + .negotiate_features = smp2p_negotiate_features_v1, + .negotiation_complete = smp2p_negotiation_complete_v1, + .find_entry = smp2p_find_entry_v1, + .create_entry = smp2p_out_create_v1, + .read_entry = smp2p_out_read_v1, + .write_entry = smp2p_out_write_v1, + .modify_entry = smp2p_out_modify_v1, + .validate_size = smp2p_in_validate_size_v1, + }, +}; + +/* interrupt configuration (filled by device tree) */ +static struct smp2p_interrupt_config smp2p_int_cfgs[SMP2P_NUM_PROCS] = { + [SMP2P_MODEM_PROC].name = "modem", + [SMP2P_AUDIO_PROC].name = "lpass", + [SMP2P_SENSOR_PROC].name = "dsps", + [SMP2P_WIRELESS_PROC].name = "wcnss", + [SMP2P_TZ_PROC].name = "tz", + [SMP2P_REMOTE_MOCK_PROC].name = "mock", +}; + +/** + * smp2p_get_log_ctx - Return log context for other SMP2P modules. + * + * @returns: Log context or NULL if none. + */ +void *smp2p_get_log_ctx(void) +{ + return log_ctx; +} + +/** + * smp2p_get_debug_mask - Return debug mask. + * + * @returns: Current debug mask. + */ +int smp2p_get_debug_mask(void) +{ + return smp2p_debug_mask; +} + +/** + * smp2p_interrupt_config - Return interrupt configuration. + * + * @returns interrupt configuration array for usage by debugfs. + */ +struct smp2p_interrupt_config *smp2p_get_interrupt_config(void) +{ + return smp2p_int_cfgs; +} + +/** + * smp2p_pid_to_name - Lookup name for remote pid. + * + * @returns: name (may be NULL). + */ +const char *smp2p_pid_to_name(int remote_pid) +{ + if (remote_pid >= SMP2P_NUM_PROCS) + return NULL; + + return smp2p_int_cfgs[remote_pid].name; +} + +/** + * smp2p_get_in_item - Return pointer to remote smem item. + * + * @remote_pid: Processor ID of the remote system. + * @returns: Pointer to inbound SMEM item + * + * This is used by debugfs to print the smem items. + */ +struct smp2p_smem __iomem *smp2p_get_in_item(int remote_pid) +{ + void *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&in_list[remote_pid].in_item_lock_lhb1, flags); + if (remote_pid < SMP2P_NUM_PROCS) + ret = in_list[remote_pid].smem_edge_in; + spin_unlock_irqrestore(&in_list[remote_pid].in_item_lock_lhb1, + flags); + + return ret; +} + +/** + * smp2p_get_out_item - Return pointer to outbound SMEM item. + * + * @remote_pid: Processor ID of remote system. + * @state: Edge state of the outbound SMEM item. + * @returns: Pointer to outbound (remote) SMEM item. + */ +struct smp2p_smem __iomem *smp2p_get_out_item(int remote_pid, int *state) +{ + void *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&out_list[remote_pid].out_item_lock_lha1, flags); + if (remote_pid < SMP2P_NUM_PROCS) { + ret = out_list[remote_pid].smem_edge_out; + if (state) + *state = out_list[remote_pid].smem_edge_state; + } + spin_unlock_irqrestore(&out_list[remote_pid].out_item_lock_lha1, flags); + + return ret; +} + +/** + * smp2p_get_smem_item_id - Return the proper SMEM item ID. + * + * @write_id: Processor that will write to the item. + * @read_id: Processor that will read from the item. + * @returns: SMEM ID + */ +static int smp2p_get_smem_item_id(int write_pid, int read_pid) +{ + int ret = -EINVAL; + + switch (write_pid) { + case SMP2P_APPS_PROC: + ret = SMEM_SMP2P_APPS_BASE + read_pid; + break; + case SMP2P_MODEM_PROC: + ret = SMEM_SMP2P_MODEM_BASE + read_pid; + break; + case SMP2P_AUDIO_PROC: + ret = SMEM_SMP2P_AUDIO_BASE + read_pid; + break; + case SMP2P_SENSOR_PROC: + ret = SMEM_SMP2P_SENSOR_BASE + read_pid; + break; + case SMP2P_WIRELESS_PROC: + ret = SMEM_SMP2P_WIRLESS_BASE + read_pid; + break; + case SMP2P_POWER_PROC: + ret = SMEM_SMP2P_POWER_BASE + read_pid; + break; + case SMP2P_TZ_PROC: + ret = SMEM_SMP2P_TZ_BASE + read_pid; + break; + } + + return ret; +} + +/** + * Return pointer to SMEM item owned by the local processor. + * + * @remote_pid: Remote processor ID + * @returns: NULL for failure; otherwise pointer to SMEM item + * + * Must be called with out_item_lock_lha1 locked for mock proc. + */ +static void *smp2p_get_local_smem_item(int remote_pid) +{ + struct smp2p_smem __iomem *item_ptr = NULL; + + if (remote_pid < SMP2P_REMOTE_MOCK_PROC) { + unsigned size; + int smem_id; + + /* lookup or allocate SMEM item */ + smem_id = smp2p_get_smem_item_id(SMP2P_APPS_PROC, remote_pid); + if (smem_id >= 0) { + item_ptr = smem_get_entry(smem_id, &size, + remote_pid, 0); + + if (!item_ptr) { + size = sizeof(struct smp2p_smem_item); + item_ptr = smem_alloc(smem_id, size, + remote_pid, 0); + } + } + } else if (remote_pid == SMP2P_REMOTE_MOCK_PROC) { + /* + * This path is only used during unit testing so + * the GFP_ATOMIC allocation should not be a + * concern. + */ + if (!out_list[SMP2P_REMOTE_MOCK_PROC].smem_edge_out) + item_ptr = kzalloc( + sizeof(struct smp2p_smem_item), + GFP_ATOMIC); + } + return item_ptr; +} + +/** + * smp2p_get_remote_smem_item - Return remote SMEM item. + * + * @remote_pid: Remote processor ID + * @out_item: Pointer to the output item structure + * @returns: NULL for failure; otherwise pointer to SMEM item + * + * Return pointer to SMEM item owned by the remote processor. + * + * Note that this function does an SMEM lookup which uses a remote spinlock, + * so this function should not be called more than necessary. + * + * Must be called with out_item_lock_lha1 and in_item_lock_lhb1 locked. + */ +static void *smp2p_get_remote_smem_item(int remote_pid, + struct smp2p_out_list_item *out_item) +{ + void *item_ptr = NULL; + unsigned size = 0; + + if (!out_item) + return item_ptr; + + if (remote_pid < SMP2P_REMOTE_MOCK_PROC) { + int smem_id; + + smem_id = smp2p_get_smem_item_id(remote_pid, SMP2P_APPS_PROC); + if (smem_id >= 0) + item_ptr = smem_get_entry(smem_id, &size, + remote_pid, 0); + } else if (remote_pid == SMP2P_REMOTE_MOCK_PROC) { + item_ptr = msm_smp2p_get_remote_mock_smem_item(&size); + } + item_ptr = out_item->ops_ptr->validate_size(remote_pid, item_ptr, size); + + return item_ptr; +} + +/** + * smp2p_ssr_ack_needed - Returns true if SSR ACK required + * + * @rpid: Remote processor ID + * + * Must be called with out_item_lock_lha1 and in_item_lock_lhb1 locked. + */ +static bool smp2p_ssr_ack_needed(uint32_t rpid) +{ + bool ssr_done; + + if (!out_list[rpid].feature_ssr_ack_enabled) + return false; + + ssr_done = SMP2P_GET_RESTART_DONE(in_list[rpid].smem_edge_in->flags); + if (ssr_done != out_list[rpid].restart_ack) + return true; + + return false; +} + +/** + * smp2p_do_ssr_ack - Handles SSR ACK + * + * @rpid: Remote processor ID + * + * Must be called with out_item_lock_lha1 and in_item_lock_lhb1 locked. + */ +static void smp2p_do_ssr_ack(uint32_t rpid) +{ + bool ack; + + if (!smp2p_ssr_ack_needed(rpid)) + return; + + ack = !out_list[rpid].restart_ack; + SMP2P_INFO("%s: ssr ack pid %d: %d -> %d\n", __func__, rpid, + out_list[rpid].restart_ack, ack); + out_list[rpid].restart_ack = ack; + SMP2P_SET_RESTART_ACK(out_list[rpid].smem_edge_out->flags, ack); + smp2p_send_interrupt(rpid); +} + +/** + * smp2p_negotiate_features_v1 - Initial feature negotiation. + * + * @features: Inbound feature set. + * @returns: Supported features (will be a same/subset of @features). + */ +static uint32_t smp2p_negotiate_features_v1(uint32_t features) +{ + return SMP2P_FEATURE_SSR_ACK; +} + +/** + * smp2p_negotiation_complete_v1 - Negotiation completed + * + * @out_item: Pointer to the output item structure + * + * Can be used to do final configuration based upon the negotiated feature set. + * + * Must be called with out_item_lock_lha1 locked. + */ +static void smp2p_negotiation_complete_v1(struct smp2p_out_list_item *out_item) +{ + uint32_t features; + + features = SMP2P_GET_FEATURES(out_item->smem_edge_out->feature_version); + + if (features & SMP2P_FEATURE_SSR_ACK) + out_item->feature_ssr_ack_enabled = true; +} + +/** + * smp2p_find_entry_v1 - Search for an entry in SMEM item. + * + * @item: Pointer to the smem item. + * @entries_total: Total number of entries in @item. + * @name: Name of the entry. + * @entry_ptr: Set to pointer of entry if found, NULL otherwise. + * @empty_spot: If non-null, set to the value of the next empty entry. + * + * Searches for entry @name in the SMEM item. If found, a pointer + * to the item is returned. If it isn't found, the first empty + * index is returned in @empty_spot. + */ +static void smp2p_find_entry_v1(struct smp2p_smem __iomem *item, + uint32_t entries_total, char *name, uint32_t **entry_ptr, + int *empty_spot) +{ + int i; + struct smp2p_entry_v1 *pos; + char entry_name[SMP2P_MAX_ENTRY_NAME]; + + if (!item || !name || !entry_ptr) { + SMP2P_ERR("%s: invalid arguments %p, %p, %p\n", + __func__, item, name, entry_ptr); + return; + } + + *entry_ptr = NULL; + if (empty_spot) + *empty_spot = -1; + + pos = (struct smp2p_entry_v1 *)(char *)(item + 1); + for (i = 0; i < entries_total; i++, ++pos) { + memcpy_fromio(entry_name, pos->name, SMP2P_MAX_ENTRY_NAME); + if (entry_name[0]) { + if (!strcmp(entry_name, name)) { + *entry_ptr = &pos->entry; + break; + } + } else if (empty_spot && *empty_spot < 0) { + *empty_spot = i; + } + } +} + +/** + * smp2p_out_create_v1 - Creates a outbound SMP2P entry. + * + * @out_entry: Pointer to the SMP2P entry structure. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Must be called with out_item_lock_lha1 locked. + */ +static int smp2p_out_create_v1(struct msm_smp2p_out *out_entry) +{ + struct smp2p_smem __iomem *smp2p_h_ptr; + struct smp2p_out_list_item *p_list; + uint32_t *state_entry_ptr; + uint32_t empty_spot; + uint32_t entries_total; + uint32_t entries_valid; + + if (!out_entry) + return -EINVAL; + + p_list = &out_list[out_entry->remote_pid]; + if (p_list->smem_edge_state != SMP2P_EDGE_STATE_OPENED) { + SMP2P_ERR("%s: item '%s':%d opened - wrong create called\n", + __func__, out_entry->name, out_entry->remote_pid); + return -ENODEV; + } + + smp2p_h_ptr = p_list->smem_edge_out; + entries_total = SMP2P_GET_ENT_TOTAL(smp2p_h_ptr->valid_total_ent); + entries_valid = SMP2P_GET_ENT_VALID(smp2p_h_ptr->valid_total_ent); + + p_list->ops_ptr->find_entry(smp2p_h_ptr, entries_total, + out_entry->name, &state_entry_ptr, &empty_spot); + if (state_entry_ptr) { + /* re-use existing entry */ + out_entry->l_smp2p_entry = state_entry_ptr; + + SMP2P_DBG("%s: item '%s':%d reused\n", __func__, + out_entry->name, out_entry->remote_pid); + } else if (entries_valid >= entries_total) { + /* need to allocate entry, but not more space */ + SMP2P_ERR("%s: no space for item '%s':%d\n", + __func__, out_entry->name, out_entry->remote_pid); + return -ENOMEM; + } else { + /* allocate a new entry */ + struct smp2p_entry_v1 *entry_ptr; + + entry_ptr = (struct smp2p_entry_v1 *)((char *)(smp2p_h_ptr + 1) + + empty_spot * sizeof(struct smp2p_entry_v1)); + memcpy_toio(entry_ptr->name, out_entry->name, + sizeof(entry_ptr->name)); + out_entry->l_smp2p_entry = &entry_ptr->entry; + ++entries_valid; + SMP2P_DBG("%s: item '%s':%d fully created as entry %d of %d\n", + __func__, out_entry->name, + out_entry->remote_pid, + entries_valid, entries_total); + SMP2P_SET_ENT_VALID(smp2p_h_ptr->valid_total_ent, + entries_valid); + smp2p_send_interrupt(out_entry->remote_pid); + } + raw_notifier_call_chain(&out_entry->msm_smp2p_notifier_list, + SMP2P_OPEN, 0); + + return 0; +} + +/** + * smp2p_out_read_v1 - Read the data from an outbound entry. + * + * @out_entry: Pointer to the SMP2P entry structure. + * @data: Out pointer, the data is available in this argument on success. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Must be called with out_item_lock_lha1 locked. + */ +static int smp2p_out_read_v1(struct msm_smp2p_out *out_entry, uint32_t *data) +{ + struct smp2p_smem __iomem *smp2p_h_ptr; + uint32_t remote_pid; + + if (!out_entry) + return -EINVAL; + + smp2p_h_ptr = out_list[out_entry->remote_pid].smem_edge_out; + remote_pid = SMP2P_GET_REMOTE_PID(smp2p_h_ptr->rem_loc_proc_id); + + if (remote_pid != out_entry->remote_pid) + return -EINVAL; + + if (out_entry->l_smp2p_entry) { + *data = readl_relaxed(out_entry->l_smp2p_entry); + } else { + SMP2P_ERR("%s: '%s':%d not yet OPEN\n", __func__, + out_entry->name, remote_pid); + return -ENODEV; + } + + return 0; +} + +/** + * smp2p_out_write_v1 - Writes an outbound entry value. + * + * @out_entry: Pointer to the SMP2P entry structure. + * @data: The data to be written. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Must be called with out_item_lock_lha1 locked. + */ +static int smp2p_out_write_v1(struct msm_smp2p_out *out_entry, uint32_t data) +{ + struct smp2p_smem __iomem *smp2p_h_ptr; + uint32_t remote_pid; + + if (!out_entry) + return -EINVAL; + + smp2p_h_ptr = out_list[out_entry->remote_pid].smem_edge_out; + remote_pid = SMP2P_GET_REMOTE_PID(smp2p_h_ptr->rem_loc_proc_id); + + if (remote_pid != out_entry->remote_pid) + return -EINVAL; + + if (out_entry->l_smp2p_entry) { + writel_relaxed(data, out_entry->l_smp2p_entry); + smp2p_send_interrupt(remote_pid); + } else { + SMP2P_ERR("%s: '%s':%d not yet OPEN\n", __func__, + out_entry->name, remote_pid); + return -ENODEV; + } + return 0; +} + +/** + * smp2p_out_modify_v1 - Modifies and outbound value. + * + * @set_mask: Mask containing the bits that needs to be set. + * @clear_mask: Mask containing the bits that needs to be cleared. + * @send_irq: Flag to send interrupt to remote processor. + * @returns: 0 on success, standard Linux error code otherwise. + * + * The clear mask is applied first, so if a bit is set in both clear and + * set mask, the result will be that the bit is set. + * + * Must be called with out_item_lock_lha1 locked. + */ +static int smp2p_out_modify_v1(struct msm_smp2p_out *out_entry, + uint32_t set_mask, uint32_t clear_mask, bool send_irq) +{ + struct smp2p_smem __iomem *smp2p_h_ptr; + uint32_t remote_pid; + + if (!out_entry) + return -EINVAL; + + smp2p_h_ptr = out_list[out_entry->remote_pid].smem_edge_out; + remote_pid = SMP2P_GET_REMOTE_PID(smp2p_h_ptr->rem_loc_proc_id); + + if (remote_pid != out_entry->remote_pid) + return -EINVAL; + + if (out_entry->l_smp2p_entry) { + uint32_t curr_value; + + curr_value = readl_relaxed(out_entry->l_smp2p_entry); + writel_relaxed((curr_value & ~clear_mask) | set_mask, + out_entry->l_smp2p_entry); + } else { + SMP2P_ERR("%s: '%s':%d not yet OPEN\n", __func__, + out_entry->name, remote_pid); + return -ENODEV; + } + + if (send_irq) + smp2p_send_interrupt(remote_pid); + return 0; +} + +/** + * smp2p_in_validate_size_v1 - Size validation for version 1. + * + * @remote_pid: Remote processor ID. + * @smem_item: Pointer to the inbound SMEM item. + * @size: Size of the SMEM item. + * @returns: Validated smem_item pointer (or NULL if size is too small). + * + * Validates we don't end up with out-of-bounds array access due to invalid + * smem item size. If out-of-bound array access can't be avoided, then an + * error message is printed and NULL is returned to prevent usage of the + * item. + * + * Must be called with in_item_lock_lhb1 locked. + */ +static struct smp2p_smem __iomem *smp2p_in_validate_size_v1(int remote_pid, + struct smp2p_smem __iomem *smem_item, uint32_t size) +{ + uint32_t total_entries; + unsigned expected_size; + struct smp2p_smem __iomem *item_ptr; + struct smp2p_in_list_item *in_item; + + if (remote_pid >= SMP2P_NUM_PROCS || !smem_item) + return NULL; + + in_item = &in_list[remote_pid]; + item_ptr = (struct smp2p_smem __iomem *)smem_item; + + total_entries = SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent); + if (total_entries > 0) { + in_item->safe_total_entries = total_entries; + in_item->item_size = size; + + expected_size = sizeof(struct smp2p_smem) + + (total_entries * sizeof(struct smp2p_entry_v1)); + + if (size < expected_size) { + unsigned new_size; + + new_size = size; + new_size -= sizeof(struct smp2p_smem); + new_size /= sizeof(struct smp2p_entry_v1); + in_item->safe_total_entries = new_size; + + SMP2P_ERR( + "%s pid %d item too small for %d entries; expected: %d actual: %d; reduced to %d entries\n", + __func__, remote_pid, total_entries, + expected_size, size, new_size); + } + } else { + /* + * Total entries is 0, so the entry is still being initialized + * or is invalid. Either way, treat it as if the item does + * not exist yet. + */ + in_item->safe_total_entries = 0; + in_item->item_size = 0; + } + return item_ptr; +} + +/** + * smp2p_negotiate_features_v0 - Initial feature negotiation. + * + * @features: Inbound feature set. + * @returns: 0 (no features supported for v0). + */ +static uint32_t smp2p_negotiate_features_v0(uint32_t features) +{ + /* no supported features */ + return 0; +} + +/** + * smp2p_negotiation_complete_v0 - Negotiation completed + * + * @out_item: Pointer to the output item structure + * + * Can be used to do final configuration based upon the negotiated feature set. + */ +static void smp2p_negotiation_complete_v0(struct smp2p_out_list_item *out_item) +{ + SMP2P_ERR("%s: invalid negotiation complete for v0 pid %d\n", + __func__, + SMP2P_GET_REMOTE_PID(out_item->smem_edge_out->rem_loc_proc_id)); +} + +/** + * smp2p_find_entry_v0 - Stub function. + * + * @item: Pointer to the smem item. + * @entries_total: Total number of entries in @item. + * @name: Name of the entry. + * @entry_ptr: Set to pointer of entry if found, NULL otherwise. + * @empty_spot: If non-null, set to the value of the next empty entry. + * + * Entries cannot be searched for until item negotiation has been completed. + */ +static void smp2p_find_entry_v0(struct smp2p_smem __iomem *item, + uint32_t entries_total, char *name, uint32_t **entry_ptr, + int *empty_spot) +{ + if (entry_ptr) + *entry_ptr = NULL; + + if (empty_spot) + *empty_spot = -1; + + SMP2P_ERR("%s: invalid - item negotiation incomplete\n", __func__); +} + +/** + * smp2p_out_create_v0 - Initial creation function. + * + * @out_entry: Pointer to the SMP2P entry structure. + * @returns: 0 on success, standard Linux error code otherwise. + * + * If the outbound SMEM item negotiation is not complete, then + * this function is called to start the negotiation process. + * Eventually when the negotiation process is complete, this + * function pointer is switched with the appropriate function + * for the version of SMP2P being created. + * + * Must be called with out_item_lock_lha1 locked. + */ +static int smp2p_out_create_v0(struct msm_smp2p_out *out_entry) +{ + int edge_state; + struct smp2p_out_list_item *item_ptr; + + if (!out_entry) + return -EINVAL; + + edge_state = out_list[out_entry->remote_pid].smem_edge_state; + + switch (edge_state) { + case SMP2P_EDGE_STATE_CLOSED: + /* start negotiation */ + item_ptr = &out_list[out_entry->remote_pid]; + edge_state = smp2p_do_negotiation(out_entry->remote_pid, + item_ptr); + break; + + case SMP2P_EDGE_STATE_OPENING: + /* still negotiating */ + break; + + case SMP2P_EDGE_STATE_OPENED: + SMP2P_ERR("%s: item '%s':%d opened - wrong create called\n", + __func__, out_entry->name, out_entry->remote_pid); + break; + + default: + SMP2P_ERR("%s: item '%s':%d invalid SMEM item state %d\n", + __func__, out_entry->name, out_entry->remote_pid, + edge_state); + break; + } + return 0; +} + +/** + * smp2p_out_read_v0 - Stub function. + * + * @out_entry: Pointer to the SMP2P entry structure. + * @data: Out pointer, the data is available in this argument on success. + * @returns: -ENODEV + */ +static int smp2p_out_read_v0(struct msm_smp2p_out *out_entry, uint32_t *data) +{ + SMP2P_ERR("%s: item '%s':%d not OPEN\n", + __func__, out_entry->name, out_entry->remote_pid); + + return -ENODEV; +} + +/** + * smp2p_out_write_v0 - Stub function. + * + * @out_entry: Pointer to the SMP2P entry structure. + * @data: The data to be written. + * @returns: -ENODEV + */ +static int smp2p_out_write_v0(struct msm_smp2p_out *out_entry, uint32_t data) +{ + SMP2P_ERR("%s: item '%s':%d not yet OPEN\n", + __func__, out_entry->name, out_entry->remote_pid); + + return -ENODEV; +} + +/** + * smp2p_out_modify_v0 - Stub function. + * + * @set_mask: Mask containing the bits that needs to be set. + * @clear_mask: Mask containing the bits that needs to be cleared. + * @send_irq: Flag to send interrupt to remote processor. + * @returns: -ENODEV + */ +static int smp2p_out_modify_v0(struct msm_smp2p_out *out_entry, + uint32_t set_mask, uint32_t clear_mask, bool send_irq) +{ + SMP2P_ERR("%s: item '%s':%d not yet OPEN\n", + __func__, out_entry->name, out_entry->remote_pid); + + return -ENODEV; +} + +/** + * smp2p_in_validate_size_v0 - Stub function. + * + * @remote_pid: Remote processor ID. + * @smem_item: Pointer to the inbound SMEM item. + * @size: Size of the SMEM item. + * @returns: Validated smem_item pointer (or NULL if size is too small). + * + * Validates we don't end up with out-of-bounds array access due to invalid + * smem item size. If out-of-bound array access can't be avoided, then an + * error message is printed and NULL is returned to prevent usage of the + * item. + * + * Must be called with in_item_lock_lhb1 locked. + */ +static struct smp2p_smem __iomem *smp2p_in_validate_size_v0(int remote_pid, + struct smp2p_smem __iomem *smem_item, uint32_t size) +{ + struct smp2p_in_list_item *in_item; + + if (remote_pid >= SMP2P_NUM_PROCS || !smem_item) + return NULL; + + in_item = &in_list[remote_pid]; + + if (size < sizeof(struct smp2p_smem)) { + SMP2P_ERR( + "%s pid %d item size too small; expected: %zu actual: %d\n", + __func__, remote_pid, + sizeof(struct smp2p_smem), size); + smem_item = NULL; + in_item->item_size = 0; + } else { + in_item->item_size = size; + } + return smem_item; +} + +/** + * smp2p_init_header - Initializes the header of the smem item. + * + * @header_ptr: Pointer to the smp2p header. + * @local_pid: Local processor ID. + * @remote_pid: Remote processor ID. + * @feature: Features of smp2p implementation. + * @version: Version of smp2p implementation. + * + * Initializes the header as defined in the protocol specification. + */ +void smp2p_init_header(struct smp2p_smem __iomem *header_ptr, + int local_pid, int remote_pid, + uint32_t features, uint32_t version) +{ + header_ptr->magic = SMP2P_MAGIC; + SMP2P_SET_LOCAL_PID(header_ptr->rem_loc_proc_id, local_pid); + SMP2P_SET_REMOTE_PID(header_ptr->rem_loc_proc_id, remote_pid); + SMP2P_SET_FEATURES(header_ptr->feature_version, features); + SMP2P_SET_ENT_TOTAL(header_ptr->valid_total_ent, SMP2P_MAX_ENTRY); + SMP2P_SET_ENT_VALID(header_ptr->valid_total_ent, 0); + header_ptr->flags = 0; + + /* ensure that all fields are valid before version is written */ + wmb(); + SMP2P_SET_VERSION(header_ptr->feature_version, version); +} + +/** + * smp2p_do_negotiation - Implements negotiation algorithm. + * + * @remote_pid: Remote processor ID. + * @out_item: Pointer to the outbound list item. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Must be called with out_item_lock_lha1 locked. Will internally lock + * in_item_lock_lhb1. + */ +static int smp2p_do_negotiation(int remote_pid, + struct smp2p_out_list_item *out_item) +{ + struct smp2p_smem __iomem *r_smem_ptr; + struct smp2p_smem __iomem *l_smem_ptr; + uint32_t r_version; + uint32_t r_feature; + uint32_t l_version, l_feature; + int prev_state; + + if (remote_pid >= SMP2P_NUM_PROCS || !out_item) + return -EINVAL; + if (out_item->smem_edge_state == SMP2P_EDGE_STATE_FAILED) + return -EPERM; + + prev_state = out_item->smem_edge_state; + + /* create local item */ + if (!out_item->smem_edge_out) { + out_item->smem_edge_out = smp2p_get_local_smem_item(remote_pid); + if (!out_item->smem_edge_out) { + SMP2P_ERR( + "%s unable to allocate SMEM item for pid %d\n", + __func__, remote_pid); + return -ENODEV; + } + out_item->smem_edge_state = SMP2P_EDGE_STATE_OPENING; + } + l_smem_ptr = out_item->smem_edge_out; + + /* retrieve remote side and version */ + spin_lock(&in_list[remote_pid].in_item_lock_lhb1); + r_smem_ptr = smp2p_get_remote_smem_item(remote_pid, out_item); + spin_unlock(&in_list[remote_pid].in_item_lock_lhb1); + + r_version = 0; + if (r_smem_ptr) { + r_version = SMP2P_GET_VERSION(r_smem_ptr->feature_version); + r_feature = SMP2P_GET_FEATURES(r_smem_ptr->feature_version); + } + + if (r_version == 0) { + /* + * Either remote side doesn't exist, or is in the + * process of being initialized (the version is set last). + * + * In either case, treat as if the other side doesn't exist + * and write out our maximum supported version. + */ + r_smem_ptr = NULL; + r_version = ARRAY_SIZE(version_if) - 1; + r_feature = ~0U; + } + + /* find maximum supported version and feature set */ + l_version = min(r_version, (uint32_t)ARRAY_SIZE(version_if) - 1); + for (; l_version > 0; --l_version) { + if (!version_if[l_version].is_supported) + continue; + + /* found valid version */ + l_feature = version_if[l_version].negotiate_features(~0U); + if (l_version == r_version) + l_feature &= r_feature; + break; + } + + if (l_version == 0) { + SMP2P_ERR( + "%s: negotiation failure pid %d: RV %d RF %x\n", + __func__, remote_pid, r_version, r_feature + ); + SMP2P_SET_VERSION(l_smem_ptr->feature_version, + SMP2P_EDGE_STATE_FAILED); + smp2p_send_interrupt(remote_pid); + out_item->smem_edge_state = SMP2P_EDGE_STATE_FAILED; + return -EPERM; + } + + /* update header and notify remote side */ + smp2p_init_header(l_smem_ptr, SMP2P_APPS_PROC, remote_pid, + l_feature, l_version); + smp2p_send_interrupt(remote_pid); + + /* handle internal state changes */ + if (r_smem_ptr && l_version == r_version && + l_feature == r_feature) { + struct msm_smp2p_out *pos; + + /* negotiation complete */ + out_item->ops_ptr = &version_if[l_version]; + out_item->ops_ptr->negotiation_complete(out_item); + out_item->smem_edge_state = SMP2P_EDGE_STATE_OPENED; + SMP2P_INFO( + "%s: negotiation complete pid %d: State %d->%d F0x%08x\n", + __func__, remote_pid, prev_state, + out_item->smem_edge_state, l_feature); + + /* create any pending outbound entries */ + list_for_each_entry(pos, &out_item->list, out_edge_list) { + out_item->ops_ptr->create_entry(pos); + } + + /* update inbound edge */ + spin_lock(&in_list[remote_pid].in_item_lock_lhb1); + (void)out_item->ops_ptr->validate_size(remote_pid, r_smem_ptr, + in_list[remote_pid].item_size); + in_list[remote_pid].smem_edge_in = r_smem_ptr; + spin_unlock(&in_list[remote_pid].in_item_lock_lhb1); + } else { + SMP2P_INFO("%s: negotiation pid %d: State %d->%d F0x%08x\n", + __func__, remote_pid, prev_state, + out_item->smem_edge_state, l_feature); + } + return 0; +} + +/** + * msm_smp2p_out_open - Opens an outbound entry. + * + * @remote_pid: Outbound processor ID. + * @name: Name of the entry. + * @open_notifier: Notifier block for the open notification. + * @handle: Handle to the smem entry structure. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Opens an outbound entry with the name specified by entry, from the + * local processor to the remote processor(remote_pid). If the entry, remote_pid + * and open_notifier are valid, then handle will be set and zero will be + * returned. The smem item that holds this entry will be created if it has + * not been created according to the version negotiation algorithm. + * The open_notifier will be used to notify the clients about the + * availability of the entry. + */ +int msm_smp2p_out_open(int remote_pid, const char *name, + struct notifier_block *open_notifier, + struct msm_smp2p_out **handle) +{ + struct msm_smp2p_out *out_entry; + struct msm_smp2p_out *pos; + int ret = 0; + unsigned long flags; + + if (handle) + *handle = NULL; + + if (remote_pid >= SMP2P_NUM_PROCS || !name || !open_notifier || !handle) + return -EINVAL; + + if ((remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, remote_pid, name); + return -EPROBE_DEFER; + } + + /* Allocate the smp2p object and node */ + out_entry = kzalloc(sizeof(*out_entry), GFP_KERNEL); + if (!out_entry) + return -ENOMEM; + + /* Handle duplicate registration */ + spin_lock_irqsave(&out_list[remote_pid].out_item_lock_lha1, flags); + list_for_each_entry(pos, &out_list[remote_pid].list, + out_edge_list) { + if (!strcmp(pos->name, name)) { + spin_unlock_irqrestore( + &out_list[remote_pid].out_item_lock_lha1, + flags); + kfree(out_entry); + SMP2P_ERR("%s: duplicate registration '%s':%d\n", + __func__, name, remote_pid); + return -EBUSY; + } + } + + out_entry->remote_pid = remote_pid; + RAW_INIT_NOTIFIER_HEAD(&out_entry->msm_smp2p_notifier_list); + strlcpy(out_entry->name, name, SMP2P_MAX_ENTRY_NAME); + out_entry->open_nb = open_notifier; + raw_notifier_chain_register(&out_entry->msm_smp2p_notifier_list, + out_entry->open_nb); + list_add(&out_entry->out_edge_list, &out_list[remote_pid].list); + + ret = out_list[remote_pid].ops_ptr->create_entry(out_entry); + if (ret) { + list_del(&out_entry->out_edge_list); + raw_notifier_chain_unregister( + &out_entry->msm_smp2p_notifier_list, + out_entry->open_nb); + spin_unlock_irqrestore( + &out_list[remote_pid].out_item_lock_lha1, flags); + kfree(out_entry); + SMP2P_ERR("%s: unable to open '%s':%d error %d\n", + __func__, name, remote_pid, ret); + return ret; + } + spin_unlock_irqrestore(&out_list[remote_pid].out_item_lock_lha1, + flags); + *handle = out_entry; + + return 0; +} +EXPORT_SYMBOL(msm_smp2p_out_open); + +/** + * msm_smp2p_out_close - Closes the handle to an outbound entry. + * + * @handle: Pointer to smp2p out entry handle. + * @returns: 0 on success, standard Linux error code otherwise. + * + * The actual entry will not be deleted and can be re-opened at a later + * time. The handle will be set to NULL. + */ +int msm_smp2p_out_close(struct msm_smp2p_out **handle) +{ + unsigned long flags; + struct msm_smp2p_out *out_entry; + struct smp2p_out_list_item *out_item; + + if (!handle || !*handle) + return -EINVAL; + + out_entry = *handle; + *handle = NULL; + + if ((out_entry->remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[out_entry->remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, out_entry->remote_pid, out_entry->name); + return -EPROBE_DEFER; + } + + out_item = &out_list[out_entry->remote_pid]; + spin_lock_irqsave(&out_item->out_item_lock_lha1, flags); + list_del(&out_entry->out_edge_list); + raw_notifier_chain_unregister(&out_entry->msm_smp2p_notifier_list, + out_entry->open_nb); + spin_unlock_irqrestore(&out_item->out_item_lock_lha1, flags); + + kfree(out_entry); + + return 0; +} +EXPORT_SYMBOL(msm_smp2p_out_close); + +/** + * msm_smp2p_out_read - Allows reading the entry. + * + * @handle: Handle to the smem entry structure. + * @data: Out pointer that holds the read data. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Allows reading of the outbound entry for read-modify-write + * operation. + */ +int msm_smp2p_out_read(struct msm_smp2p_out *handle, uint32_t *data) +{ + int ret = -EINVAL; + unsigned long flags; + struct smp2p_out_list_item *out_item; + + if (!handle || !data) + return ret; + + if ((handle->remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[handle->remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, handle->remote_pid, handle->name); + return -EPROBE_DEFER; + } + + out_item = &out_list[handle->remote_pid]; + spin_lock_irqsave(&out_item->out_item_lock_lha1, flags); + ret = out_item->ops_ptr->read_entry(handle, data); + spin_unlock_irqrestore(&out_item->out_item_lock_lha1, flags); + + return ret; +} +EXPORT_SYMBOL(msm_smp2p_out_read); + +/** + * msm_smp2p_out_write - Allows writing to the entry. + * + * @handle: Handle to smem entry structure. + * @data: Data that has to be written. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Writes a new value to the output entry. Multiple back-to-back writes + * may overwrite previous writes before the remote processor get a chance + * to see them leading to ABA race condition. The client must implement + * their own synchronization mechanism (such as echo mechanism) if this is + * not acceptable. + */ +int msm_smp2p_out_write(struct msm_smp2p_out *handle, uint32_t data) +{ + int ret = -EINVAL; + unsigned long flags; + struct smp2p_out_list_item *out_item; + + if (!handle) + return ret; + + if ((handle->remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[handle->remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, handle->remote_pid, handle->name); + return -EPROBE_DEFER; + } + + out_item = &out_list[handle->remote_pid]; + spin_lock_irqsave(&out_item->out_item_lock_lha1, flags); + ret = out_item->ops_ptr->write_entry(handle, data); + spin_unlock_irqrestore(&out_item->out_item_lock_lha1, flags); + + return ret; + +} +EXPORT_SYMBOL(msm_smp2p_out_write); + +/** + * msm_smp2p_out_modify - Modifies the entry. + * + * @handle: Handle to the smem entry structure. + * @set_mask: Specifies the bits that needs to be set. + * @clear_mask: Specifies the bits that needs to be cleared. + * @send_irq: Flag to send interrupt to remote processor. + * @returns: 0 on success, standard Linux error code otherwise. + * + * The modification is done by doing a bitwise AND of clear mask followed by + * the bit wise OR of set mask. The clear bit mask is applied first to the + * data, so if a bit is set in both the clear mask and the set mask, then in + * the result is a set bit. Multiple back-to-back modifications may overwrite + * previous values before the remote processor gets a chance to see them + * leading to ABA race condition. The client must implement their own + * synchronization mechanism (such as echo mechanism) if this is not + * acceptable. + */ +int msm_smp2p_out_modify(struct msm_smp2p_out *handle, uint32_t set_mask, + uint32_t clear_mask, bool send_irq) +{ + int ret = -EINVAL; + unsigned long flags; + struct smp2p_out_list_item *out_item; + + if (!handle) + return ret; + + if ((handle->remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[handle->remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, handle->remote_pid, handle->name); + return -EPROBE_DEFER; + } + + out_item = &out_list[handle->remote_pid]; + spin_lock_irqsave(&out_item->out_item_lock_lha1, flags); + ret = out_item->ops_ptr->modify_entry(handle, set_mask, + clear_mask, send_irq); + spin_unlock_irqrestore(&out_item->out_item_lock_lha1, flags); + + return ret; +} +EXPORT_SYMBOL(msm_smp2p_out_modify); + +/** + * msm_smp2p_in_read - Read an entry on a remote processor. + * + * @remote_pid: Processor ID of the remote processor. + * @name: Name of the entry that is to be read. + * @data: Output pointer, the value will be placed here if successful. + * @returns: 0 on success, standard Linux error code otherwise. + */ +int msm_smp2p_in_read(int remote_pid, const char *name, uint32_t *data) +{ + unsigned long flags; + struct smp2p_out_list_item *out_item; + uint32_t *entry_ptr = NULL; + + if (remote_pid >= SMP2P_NUM_PROCS) + return -EINVAL; + + if ((remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, remote_pid, name); + return -EPROBE_DEFER; + } + + out_item = &out_list[remote_pid]; + spin_lock_irqsave(&out_item->out_item_lock_lha1, flags); + spin_lock(&in_list[remote_pid].in_item_lock_lhb1); + + if (in_list[remote_pid].smem_edge_in) + out_item->ops_ptr->find_entry( + in_list[remote_pid].smem_edge_in, + in_list[remote_pid].safe_total_entries, + (char *)name, &entry_ptr, NULL); + + spin_unlock(&in_list[remote_pid].in_item_lock_lhb1); + spin_unlock_irqrestore(&out_item->out_item_lock_lha1, flags); + + if (!entry_ptr) + return -ENODEV; + + *data = readl_relaxed(entry_ptr); + return 0; +} +EXPORT_SYMBOL(msm_smp2p_in_read); + +/** + * msm_smp2p_in_register - Notifies the change in value of the entry. + * + * @pid: Remote processor ID. + * @name: Name of the entry. + * @in_notifier: Notifier block used to notify about the event. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Register for change notifications for a remote entry. If the remote entry + * does not exist yet, then the registration request will be held until the + * remote side opens. Once the entry is open, then the SMP2P_OPEN notification + * will be sent. Any changes to the entry will trigger a call to the notifier + * block with an SMP2P_ENTRY_UPDATE event and the data field will point to an + * msm_smp2p_update_notif structure containing the current and previous value. + */ +int msm_smp2p_in_register(int pid, const char *name, + struct notifier_block *in_notifier) +{ + struct smp2p_in *pos; + struct smp2p_in *in = NULL; + int ret; + unsigned long flags; + struct msm_smp2p_update_notif data; + uint32_t *entry_ptr; + + if (pid >= SMP2P_NUM_PROCS || !name || !in_notifier) + return -EINVAL; + + if ((pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, pid, name); + return -EPROBE_DEFER; + } + + /* Pre-allocate before spinlock since we will likely needed it */ + in = kzalloc(sizeof(*in), GFP_KERNEL); + if (!in) + return -ENOMEM; + + /* Search for existing entry */ + spin_lock_irqsave(&out_list[pid].out_item_lock_lha1, flags); + spin_lock(&in_list[pid].in_item_lock_lhb1); + + list_for_each_entry(pos, &in_list[pid].list, in_edge_list) { + if (!strncmp(pos->name, name, + SMP2P_MAX_ENTRY_NAME)) { + kfree(in); + in = pos; + break; + } + } + + /* Create and add it to the list */ + if (!in->notifier_count) { + in->remote_pid = pid; + strlcpy(in->name, name, SMP2P_MAX_ENTRY_NAME); + RAW_INIT_NOTIFIER_HEAD(&in->in_notifier_list); + list_add(&in->in_edge_list, &in_list[pid].list); + } + + ret = raw_notifier_chain_register(&in->in_notifier_list, + in_notifier); + if (ret) { + if (!in->notifier_count) { + list_del(&in->in_edge_list); + kfree(in); + } + SMP2P_DBG("%s: '%s':%d failed %d\n", __func__, name, pid, ret); + goto bail; + } + in->notifier_count++; + + if (out_list[pid].smem_edge_state == SMP2P_EDGE_STATE_OPENED) { + out_list[pid].ops_ptr->find_entry( + in_list[pid].smem_edge_in, + in_list[pid].safe_total_entries, (char *)name, + &entry_ptr, NULL); + if (entry_ptr) { + in->entry_ptr = entry_ptr; + in->prev_entry_val = readl_relaxed(entry_ptr); + + data.previous_value = in->prev_entry_val; + data.current_value = in->prev_entry_val; + in_notifier->notifier_call(in_notifier, SMP2P_OPEN, + (void *)&data); + } + } + SMP2P_DBG("%s: '%s':%d registered\n", __func__, name, pid); + +bail: + spin_unlock(&in_list[pid].in_item_lock_lhb1); + spin_unlock_irqrestore(&out_list[pid].out_item_lock_lha1, flags); + return ret; + +} +EXPORT_SYMBOL(msm_smp2p_in_register); + +/** + * msm_smp2p_in_unregister - Unregister the notifier for remote entry. + * + * @remote_pid: Processor Id of the remote processor. + * @name: The name of the entry. + * @in_notifier: Notifier block passed during registration. + * @returns: 0 on success, standard Linux error code otherwise. + */ +int msm_smp2p_in_unregister(int remote_pid, const char *name, + struct notifier_block *in_notifier) +{ + struct smp2p_in *pos; + struct smp2p_in *in = NULL; + int ret = -ENODEV; + unsigned long flags; + + if (remote_pid >= SMP2P_NUM_PROCS || !name || !in_notifier) + return -EINVAL; + + if ((remote_pid != SMP2P_REMOTE_MOCK_PROC) && + !smp2p_int_cfgs[remote_pid].is_configured) { + SMP2P_INFO("%s before msm_smp2p_init(): pid[%d] name[%s]\n", + __func__, remote_pid, name); + return -EPROBE_DEFER; + } + + spin_lock_irqsave(&in_list[remote_pid].in_item_lock_lhb1, flags); + list_for_each_entry(pos, &in_list[remote_pid].list, + in_edge_list) { + if (!strncmp(pos->name, name, SMP2P_MAX_ENTRY_NAME)) { + in = pos; + break; + } + } + if (!in) + goto fail; + + ret = raw_notifier_chain_unregister(&pos->in_notifier_list, + in_notifier); + if (ret == 0) { + pos->notifier_count--; + if (!pos->notifier_count) { + list_del(&pos->in_edge_list); + kfree(pos); + ret = 0; + } + } else { + SMP2P_ERR("%s: unregister failure '%s':%d\n", __func__, + name, remote_pid); + ret = -ENODEV; + } + +fail: + spin_unlock_irqrestore(&in_list[remote_pid].in_item_lock_lhb1, flags); + + return ret; +} +EXPORT_SYMBOL(msm_smp2p_in_unregister); + +/** + * smp2p_send_interrupt - Send interrupt to remote system. + * + * @remote_pid: Processor ID of the remote system + * + * Must be called with out_item_lock_lha1 locked. + */ +static void smp2p_send_interrupt(int remote_pid) +{ + if (smp2p_int_cfgs[remote_pid].name) + SMP2P_DBG("SMP2P Int Apps->%s(%d)\n", + smp2p_int_cfgs[remote_pid].name, remote_pid); + + ++smp2p_int_cfgs[remote_pid].out_interrupt_count; + if (remote_pid != SMP2P_REMOTE_MOCK_PROC && + smp2p_int_cfgs[remote_pid].out_int_mask) { + /* flush any pending writes before triggering interrupt */ + wmb(); + writel_relaxed(smp2p_int_cfgs[remote_pid].out_int_mask, + smp2p_int_cfgs[remote_pid].out_int_ptr); + } else { + smp2p_remote_mock_rx_interrupt(); + } +} + +/** + * smp2p_in_edge_notify - Notifies the entry changed on remote processor. + * + * @pid: Processor ID of the remote processor. + * + * This function is invoked on an incoming interrupt, it scans + * the list of the clients registered for the entries on the remote + * processor and notifies them if the data changes. + * + * Note: Edge state must be OPENED to avoid a race condition with + * out_list[pid].ops_ptr->find_entry. + */ +static void smp2p_in_edge_notify(int pid) +{ + struct smp2p_in *pos; + uint32_t *entry_ptr; + unsigned long flags; + struct smp2p_smem __iomem *smem_h_ptr; + uint32_t curr_data; + struct msm_smp2p_update_notif data; + + spin_lock_irqsave(&in_list[pid].in_item_lock_lhb1, flags); + smem_h_ptr = in_list[pid].smem_edge_in; + if (!smem_h_ptr) { + SMP2P_DBG("%s: No remote SMEM item for pid %d\n", + __func__, pid); + spin_unlock_irqrestore(&in_list[pid].in_item_lock_lhb1, flags); + return; + } + + list_for_each_entry(pos, &in_list[pid].list, in_edge_list) { + if (pos->entry_ptr == NULL) { + /* entry not open - try to open it */ + out_list[pid].ops_ptr->find_entry(smem_h_ptr, + in_list[pid].safe_total_entries, pos->name, + &entry_ptr, NULL); + + if (entry_ptr) { + pos->entry_ptr = entry_ptr; + pos->prev_entry_val = 0; + data.previous_value = 0; + data.current_value = readl_relaxed(entry_ptr); + raw_notifier_call_chain( + &pos->in_notifier_list, + SMP2P_OPEN, (void *)&data); + } + } + + if (pos->entry_ptr != NULL) { + /* send update notification */ + curr_data = readl_relaxed(pos->entry_ptr); + if (curr_data != pos->prev_entry_val) { + data.previous_value = pos->prev_entry_val; + data.current_value = curr_data; + pos->prev_entry_val = curr_data; + raw_notifier_call_chain( + &pos->in_notifier_list, + SMP2P_ENTRY_UPDATE, (void *)&data); + } + } + } + spin_unlock_irqrestore(&in_list[pid].in_item_lock_lhb1, flags); +} + +/** + * smp2p_interrupt_handler - Incoming interrupt handler. + * + * @irq: Interrupt ID + * @data: Edge + * @returns: IRQ_HANDLED or IRQ_NONE for invalid interrupt + */ +static irqreturn_t smp2p_interrupt_handler(int irq, void *data) +{ + unsigned long flags; + uint32_t remote_pid = (uint32_t)(uintptr_t)data; + + if (remote_pid >= SMP2P_NUM_PROCS) { + SMP2P_ERR("%s: invalid interrupt pid %d\n", + __func__, remote_pid); + return IRQ_NONE; + } + + if (smp2p_int_cfgs[remote_pid].name) + SMP2P_DBG("SMP2P Int %s(%d)->Apps\n", + smp2p_int_cfgs[remote_pid].name, remote_pid); + + spin_lock_irqsave(&out_list[remote_pid].out_item_lock_lha1, flags); + ++smp2p_int_cfgs[remote_pid].in_interrupt_count; + + if (out_list[remote_pid].smem_edge_state != SMP2P_EDGE_STATE_OPENED) + smp2p_do_negotiation(remote_pid, &out_list[remote_pid]); + + if (out_list[remote_pid].smem_edge_state == SMP2P_EDGE_STATE_OPENED) { + bool do_restart_ack; + + /* + * Follow double-check pattern for restart ack since: + * 1) we must notify clients of the X->0 transition + * that is part of the restart + * 2) lock cannot be held during the + * smp2p_in_edge_notify() call because clients may do + * re-entrant calls into our APIs. + * + * smp2p_do_ssr_ack() will only do the ack if it is + * necessary to handle the race condition exposed by + * unlocking the spinlocks. + */ + spin_lock(&in_list[remote_pid].in_item_lock_lhb1); + do_restart_ack = smp2p_ssr_ack_needed(remote_pid); + spin_unlock(&in_list[remote_pid].in_item_lock_lhb1); + spin_unlock_irqrestore(&out_list[remote_pid].out_item_lock_lha1, + flags); + + smp2p_in_edge_notify(remote_pid); + + if (do_restart_ack) { + spin_lock_irqsave( + &out_list[remote_pid].out_item_lock_lha1, + flags); + spin_lock(&in_list[remote_pid].in_item_lock_lhb1); + + smp2p_do_ssr_ack(remote_pid); + + spin_unlock(&in_list[remote_pid].in_item_lock_lhb1); + spin_unlock_irqrestore( + &out_list[remote_pid].out_item_lock_lha1, + flags); + } + } else { + spin_unlock_irqrestore(&out_list[remote_pid].out_item_lock_lha1, + flags); + } + + return IRQ_HANDLED; +} + +/** + * smp2p_reset_mock_edge - Reinitializes the mock edge. + * + * @returns: 0 on success, -EAGAIN to retry later. + * + * Reinitializes the mock edge to initial power-up state values. + */ +int smp2p_reset_mock_edge(void) +{ + const int rpid = SMP2P_REMOTE_MOCK_PROC; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&out_list[rpid].out_item_lock_lha1, flags); + spin_lock(&in_list[rpid].in_item_lock_lhb1); + + if (!list_empty(&out_list[rpid].list) || + !list_empty(&in_list[rpid].list)) { + ret = -EAGAIN; + goto fail; + } + + kfree(out_list[rpid].smem_edge_out); + out_list[rpid].smem_edge_out = NULL; + out_list[rpid].ops_ptr = &version_if[0]; + out_list[rpid].smem_edge_state = SMP2P_EDGE_STATE_CLOSED; + out_list[rpid].feature_ssr_ack_enabled = false; + out_list[rpid].restart_ack = false; + + in_list[rpid].smem_edge_in = NULL; + in_list[rpid].item_size = 0; + in_list[rpid].safe_total_entries = 0; + +fail: + spin_unlock(&in_list[rpid].in_item_lock_lhb1); + spin_unlock_irqrestore(&out_list[rpid].out_item_lock_lha1, flags); + + return ret; +} + +/** + * msm_smp2p_interrupt_handler - Triggers incoming interrupt. + * + * @remote_pid: Remote processor ID + * + * This function is used with the remote mock infrastructure + * used for testing. It simulates triggering of interrupt in + * a testing environment. + */ +void msm_smp2p_interrupt_handler(int remote_pid) +{ + smp2p_interrupt_handler(0, (void *)(uintptr_t)remote_pid); +} + +/** + * msm_smp2p_probe - Device tree probe function. + * + * @pdev: Pointer to device tree data. + * @returns: 0 on success; -ENODEV otherwise + */ +static int msm_smp2p_probe(struct platform_device *pdev) +{ + struct resource *r; + void *irq_out_ptr = NULL; + char *key; + uint32_t edge; + int ret; + struct device_node *node; + uint32_t irq_bitmask; + uint32_t irq_line; + void *temp_p; + unsigned temp_sz; + + node = pdev->dev.of_node; + + key = "qcom,remote-pid"; + ret = of_property_read_u32(node, key, &edge); + if (ret) { + SMP2P_ERR("%s: missing edge '%s'\n", __func__, key); + ret = -ENODEV; + goto fail; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + SMP2P_ERR("%s: failed gathering irq-reg resource for edge %d\n" + , __func__, edge); + ret = -ENODEV; + goto fail; + } + irq_out_ptr = ioremap_nocache(r->start, resource_size(r)); + if (!irq_out_ptr) { + SMP2P_ERR("%s: failed remap from phys to virt for edge %d\n", + __func__, edge); + ret = -ENOMEM; + goto fail; + } + + key = "qcom,irq-bitmask"; + ret = of_property_read_u32(node, key, &irq_bitmask); + if (ret) + goto missing_key; + + key = "interrupts"; + irq_line = platform_get_irq(pdev, 0); + if (irq_line == -ENXIO) + goto missing_key; + + /* + * We depend on the SMEM driver, so do a test access to see if SMEM is + * ready. We don't want any side effects at this time (so no alloc) + * and the return doesn't matter, so long as it is not -EPROBE_DEFER. + */ + temp_p = smem_get_entry( + smp2p_get_smem_item_id(SMP2P_APPS_PROC, SMP2P_MODEM_PROC), + &temp_sz, + 0, + SMEM_ANY_HOST_FLAG); + if (PTR_ERR(temp_p) == -EPROBE_DEFER) { + SMP2P_INFO("%s: edge:%d probe before smem ready\n", __func__, + edge); + ret = -EPROBE_DEFER; + goto fail; + } + + ret = request_irq(irq_line, smp2p_interrupt_handler, + IRQF_TRIGGER_RISING, "smp2p", (void *)(uintptr_t)edge); + if (ret < 0) { + SMP2P_ERR("%s: request_irq() failed on %d (edge %d)\n", + __func__, irq_line, edge); + ret = -ENODEV; + goto fail; + } + + ret = enable_irq_wake(irq_line); + if (ret < 0) + SMP2P_ERR("%s: enable_irq_wake() failed on %d (edge %d)\n", + __func__, irq_line, edge); + + /* + * Set entry (keep is_configured last to prevent usage before + * initialization). + */ + smp2p_int_cfgs[edge].in_int_id = irq_line; + smp2p_int_cfgs[edge].out_int_mask = irq_bitmask; + smp2p_int_cfgs[edge].out_int_ptr = irq_out_ptr; + smp2p_int_cfgs[edge].is_configured = true; + return 0; + +missing_key: + SMP2P_ERR("%s: missing '%s' for edge %d\n", __func__, key, edge); + ret = -ENODEV; +fail: + if (irq_out_ptr) + iounmap(irq_out_ptr); + return ret; +} + +static struct of_device_id msm_smp2p_match_table[] = { + { .compatible = "qcom,smp2p" }, + {}, +}; + +static struct platform_driver msm_smp2p_driver = { + .probe = msm_smp2p_probe, + .driver = { + .name = "msm_smp2p", + .owner = THIS_MODULE, + .of_match_table = msm_smp2p_match_table, + }, +}; + +/** + * msm_smp2p_init - Initialization function for the module. + * + * @returns: 0 on success, standard Linux error code otherwise. + */ +static int __init msm_smp2p_init(void) +{ + int i; + int rc; + + for (i = 0; i < SMP2P_NUM_PROCS; i++) { + spin_lock_init(&out_list[i].out_item_lock_lha1); + INIT_LIST_HEAD(&out_list[i].list); + out_list[i].smem_edge_out = NULL; + out_list[i].smem_edge_state = SMP2P_EDGE_STATE_CLOSED; + out_list[i].ops_ptr = &version_if[0]; + out_list[i].feature_ssr_ack_enabled = false; + out_list[i].restart_ack = false; + + spin_lock_init(&in_list[i].in_item_lock_lhb1); + INIT_LIST_HEAD(&in_list[i].list); + in_list[i].smem_edge_in = NULL; + } + + log_ctx = ipc_log_context_create(NUM_LOG_PAGES, "smp2p", 0); + if (!log_ctx) + SMP2P_ERR("%s: unable to create log context\n", __func__); + + rc = platform_driver_register(&msm_smp2p_driver); + if (rc) { + SMP2P_ERR("%s: msm_smp2p_driver register failed %d\n", + __func__, rc); + return rc; + } + + return 0; +} +module_init(msm_smp2p_init); + +MODULE_DESCRIPTION("MSM Shared Memory Point to Point"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/smp2p_debug.c b/drivers/soc/qcom/smp2p_debug.c new file mode 100644 index 000000000000..4deb05a08139 --- /dev/null +++ b/drivers/soc/qcom/smp2p_debug.c @@ -0,0 +1,335 @@ +/* drivers/soc/qcom/smp2p_debug.c + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/ctype.h> +#include <linux/list.h> +#include <linux/debugfs.h> +#include <linux/io.h> +#include "smp2p_private.h" + +#if defined(CONFIG_DEBUG_FS) + +/** + * Dump interrupt statistics. + * + * @s: pointer to output file + */ +static void smp2p_int_stats(struct seq_file *s) +{ + struct smp2p_interrupt_config *int_cfg; + int pid; + + int_cfg = smp2p_get_interrupt_config(); + if (!int_cfg) + return; + + seq_puts(s, "| Processor | Incoming Id | Incoming # |"); + seq_puts(s, " Outgoing # | Base Ptr | Mask |\n"); + + for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) { + if (!int_cfg[pid].is_configured && + pid != SMP2P_REMOTE_MOCK_PROC) + continue; + + seq_printf(s, "| %5s (%d) | %11u | %10u | %10u | %p | %08x |\n", + int_cfg[pid].name, + pid, int_cfg[pid].in_int_id, + int_cfg[pid].in_interrupt_count, + int_cfg[pid].out_interrupt_count, + int_cfg[pid].out_int_ptr, + int_cfg[pid].out_int_mask); + } +} + +/** + * Dump item header line 1. + * + * @buf: output buffer + * @max: length of output buffer + * @item_ptr: SMEM item pointer + * @state: item state + * @returns: Number of bytes written to output buffer + */ +static int smp2p_item_header1(char *buf, int max, struct smp2p_smem *item_ptr, + enum msm_smp2p_edge_state state) +{ + int i = 0; + const char *state_text; + + if (!item_ptr) { + i += scnprintf(buf + i, max - i, "None"); + return i; + } + + switch (state) { + case SMP2P_EDGE_STATE_CLOSED: + state_text = "State: Closed"; + break; + case SMP2P_EDGE_STATE_OPENING: + state_text = "State: Opening"; + break; + case SMP2P_EDGE_STATE_OPENED: + state_text = "State: Opened"; + break; + default: + state_text = ""; + break; + } + + i += scnprintf(buf + i, max - i, + "%-14s LPID %d RPID %d", + state_text, + SMP2P_GET_LOCAL_PID(item_ptr->rem_loc_proc_id), + SMP2P_GET_REMOTE_PID(item_ptr->rem_loc_proc_id) + ); + + return i; +} + +/** + * Dump item header line 2. + * + * @buf: output buffer + * @max: length of output buffer + * @item_ptr: SMEM item pointer + * @returns: Number of bytes written to output buffer + */ +static int smp2p_item_header2(char *buf, int max, struct smp2p_smem *item_ptr) +{ + int i = 0; + + if (!item_ptr) { + i += scnprintf(buf + i, max - i, "None"); + return i; + } + + i += scnprintf(buf + i, max - i, + "Version: %08x Features: %08x", + SMP2P_GET_VERSION(item_ptr->feature_version), + SMP2P_GET_FEATURES(item_ptr->feature_version) + ); + + return i; +} + +/** + * Dump item header line 3. + * + * @buf: output buffer + * @max: length of output buffer + * @item_ptr: SMEM item pointer + * @state: item state + * @returns: Number of bytes written to output buffer + */ +static int smp2p_item_header3(char *buf, int max, struct smp2p_smem *item_ptr) +{ + int i = 0; + + if (!item_ptr) { + i += scnprintf(buf + i, max - i, "None"); + return i; + } + + i += scnprintf(buf + i, max - i, + "Entries #/Max: %d/%d Flags: %c%c", + SMP2P_GET_ENT_VALID(item_ptr->valid_total_ent), + SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent), + item_ptr->flags & SMP2P_FLAGS_RESTART_ACK_MASK ? 'A' : 'a', + item_ptr->flags & SMP2P_FLAGS_RESTART_DONE_MASK ? 'D' : 'd' + ); + + return i; +} + +/** + * Dump individual input/output item pair. + * + * @s: pointer to output file + */ +static void smp2p_item(struct seq_file *s, int remote_pid) +{ + struct smp2p_smem *out_ptr; + struct smp2p_smem *in_ptr; + struct smp2p_interrupt_config *int_cfg; + char tmp_buff[64]; + int state; + int entry; + struct smp2p_entry_v1 *out_entries = NULL; + struct smp2p_entry_v1 *in_entries = NULL; + int out_valid = 0; + int in_valid = 0; + char entry_name[SMP2P_MAX_ENTRY_NAME]; + + int_cfg = smp2p_get_interrupt_config(); + if (!int_cfg) + return; + if (!int_cfg[remote_pid].is_configured && + remote_pid != SMP2P_REMOTE_MOCK_PROC) + return; + + out_ptr = smp2p_get_out_item(remote_pid, &state); + in_ptr = smp2p_get_in_item(remote_pid); + + if (!out_ptr && !in_ptr) + return; + + /* print item headers */ + seq_printf(s, "%s%s\n", + " ====================================== ", + "======================================"); + scnprintf(tmp_buff, sizeof(tmp_buff), + "Apps(%d)->%s(%d)", + SMP2P_APPS_PROC, int_cfg[remote_pid].name, remote_pid); + seq_printf(s, "| %-37s", tmp_buff); + + scnprintf(tmp_buff, sizeof(tmp_buff), + "%s(%d)->Apps(%d)", + int_cfg[remote_pid].name, remote_pid, SMP2P_APPS_PROC); + seq_printf(s, "| %-37s|\n", tmp_buff); + seq_printf(s, "%s%s\n", + " ====================================== ", + "======================================"); + + smp2p_item_header1(tmp_buff, sizeof(tmp_buff), out_ptr, state); + seq_printf(s, "| %-37s", tmp_buff); + smp2p_item_header1(tmp_buff, sizeof(tmp_buff), in_ptr, -1); + seq_printf(s, "| %-37s|\n", tmp_buff); + + smp2p_item_header2(tmp_buff, sizeof(tmp_buff), out_ptr); + seq_printf(s, "| %-37s", tmp_buff); + smp2p_item_header2(tmp_buff, sizeof(tmp_buff), in_ptr); + seq_printf(s, "| %-37s|\n", tmp_buff); + + smp2p_item_header3(tmp_buff, sizeof(tmp_buff), out_ptr); + seq_printf(s, "| %-37s", tmp_buff); + smp2p_item_header3(tmp_buff, sizeof(tmp_buff), in_ptr); + seq_printf(s, "| %-37s|\n", tmp_buff); + + seq_printf(s, " %s%s\n", + "-------------------------------------- ", + "--------------------------------------"); + seq_printf(s, "| %-37s", + "Entry Name Value"); + seq_printf(s, "| %-37s|\n", + "Entry Name Value"); + seq_printf(s, " %s%s\n", + "-------------------------------------- ", + "--------------------------------------"); + + /* print entries */ + if (out_ptr) { + out_entries = (struct smp2p_entry_v1 *)((void *)out_ptr + + sizeof(struct smp2p_smem)); + out_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent); + } + + if (in_ptr) { + in_entries = (struct smp2p_entry_v1 *)((void *)in_ptr + + sizeof(struct smp2p_smem)); + in_valid = SMP2P_GET_ENT_VALID(in_ptr->valid_total_ent); + } + + for (entry = 0; out_entries || in_entries; ++entry) { + if (out_entries && entry < out_valid) { + memcpy_fromio(entry_name, out_entries->name, + SMP2P_MAX_ENTRY_NAME); + scnprintf(tmp_buff, sizeof(tmp_buff), + "%-16s 0x%08x", + entry_name, + out_entries->entry); + ++out_entries; + } else { + out_entries = NULL; + scnprintf(tmp_buff, sizeof(tmp_buff), "None"); + } + seq_printf(s, "| %-37s", tmp_buff); + + if (in_entries && entry < in_valid) { + memcpy_fromio(entry_name, in_entries->name, + SMP2P_MAX_ENTRY_NAME); + scnprintf(tmp_buff, sizeof(tmp_buff), + "%-16s 0x%08x", + entry_name, + in_entries->entry); + ++in_entries; + } else { + in_entries = NULL; + scnprintf(tmp_buff, sizeof(tmp_buff), "None"); + } + seq_printf(s, "| %-37s|\n", tmp_buff); + } + seq_printf(s, " %s%s\n\n", + "-------------------------------------- ", + "--------------------------------------"); +} + +/** + * Dump item state. + * + * @s: pointer to output file + */ +static void smp2p_items(struct seq_file *s) +{ + int pid; + + for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) + smp2p_item(s, pid); +} + +static struct dentry *dent; + +static int debugfs_show(struct seq_file *s, void *data) +{ + void (*show)(struct seq_file *) = s->private; + + show(s); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_show, inode->i_private); +} + +static const struct file_operations debug_ops = { + .open = debug_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +void debug_create(const char *name, + void (*show)(struct seq_file *)) +{ + struct dentry *file; + + file = debugfs_create_file(name, 0444, dent, show, &debug_ops); + if (!file) + pr_err("%s: unable to create file '%s'\n", __func__, name); +} + +static int __init smp2p_debugfs_init(void) +{ + dent = debugfs_create_dir("smp2p", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debug_create("int_stats", smp2p_int_stats); + debug_create("items", smp2p_items); + + return 0; +} + +late_initcall(smp2p_debugfs_init); +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/soc/qcom/smp2p_loopback.c b/drivers/soc/qcom/smp2p_loopback.c new file mode 100644 index 000000000000..c840eea94bfa --- /dev/null +++ b/drivers/soc/qcom/smp2p_loopback.c @@ -0,0 +1,449 @@ +/* drivers/soc/qcom/smp2p_loopback.c + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/termios.h> +#include <linux/module.h> +#include <linux/remote_spinlock.h> +#include "smem_private.h" +#include "smp2p_private.h" + +/** + * struct smp2p_loopback_ctx - Representation of remote loopback object. + * + * @proc_id: Processor id of the processor that sends the loopback commands. + * @out: Handle to the smem entry structure for providing the response. + * @out_nb: Notifies the opening of local entry. + * @out_is_active: Outbound entry events should be processed. + * @in_nb: Notifies changes in the remote entry. + * @in_is_active: Inbound entry events should be processed. + * @rmt_lpb_work: Work item that handles the incoming loopback commands. + * @rmt_cmd: Structure that holds the current and previous value of the entry. + */ +struct smp2p_loopback_ctx { + int proc_id; + struct msm_smp2p_out *out; + struct notifier_block out_nb; + bool out_is_active; + struct notifier_block in_nb; + bool in_is_active; + struct work_struct rmt_lpb_work; + struct msm_smp2p_update_notif rmt_cmd; +}; + +static struct smp2p_loopback_ctx remote_loopback[SMP2P_NUM_PROCS]; +static struct msm_smp2p_remote_mock remote_mock; + +/** + * remote_spinlock_test - Handles remote spinlock test. + * + * @ctx: Loopback context + */ +static void remote_spinlock_test(struct smp2p_loopback_ctx *ctx) +{ + uint32_t test_request; + uint32_t test_response; + unsigned long flags; + int n; + unsigned lock_count = 0; + remote_spinlock_t *smem_spinlock; + + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE_REQ(test_request); + smem_spinlock = smem_get_remote_spinlock(); + if (!smem_spinlock) { + pr_err("%s: unable to get remote spinlock\n", __func__); + return; + } + + for (;;) { + remote_spin_lock_irqsave(smem_spinlock, flags); + ++lock_count; + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_LOCKED); + (void)msm_smp2p_out_write(ctx->out, test_request); + + for (n = 0; n < 10000; ++n) { + (void)msm_smp2p_in_read(ctx->proc_id, + "smp2p", &test_response); + test_response = SMP2P_GET_RMT_CMD(test_response); + + if (test_response == SMP2P_LB_CMD_RSPIN_END) + break; + + if (test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED) + SMP2P_ERR("%s: invalid spinlock command %x\n", + __func__, test_response); + } + + if (test_response == SMP2P_LB_CMD_RSPIN_END) { + SMP2P_SET_RMT_CMD_TYPE_RESP(test_request); + SMP2P_SET_RMT_CMD(test_request, + SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, lock_count); + (void)msm_smp2p_out_write(ctx->out, test_request); + break; + } + + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_UNLOCKED); + (void)msm_smp2p_out_write(ctx->out, test_request); + remote_spin_unlock_irqrestore(smem_spinlock, flags); + } + remote_spin_unlock_irqrestore(smem_spinlock, flags); +} + +/** + * smp2p_rmt_lpb_worker - Handles incoming remote loopback commands. + * + * @work: Work Item scheduled to handle the incoming commands. + */ +static void smp2p_rmt_lpb_worker(struct work_struct *work) +{ + struct smp2p_loopback_ctx *ctx; + int lpb_cmd; + int lpb_cmd_type; + int lpb_data; + + ctx = container_of(work, struct smp2p_loopback_ctx, rmt_lpb_work); + + if (!ctx->in_is_active || !ctx->out_is_active) + return; + + if (ctx->rmt_cmd.previous_value == ctx->rmt_cmd.current_value) + return; + + lpb_cmd_type = SMP2P_GET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value); + lpb_cmd = SMP2P_GET_RMT_CMD(ctx->rmt_cmd.current_value); + lpb_data = SMP2P_GET_RMT_DATA(ctx->rmt_cmd.current_value); + + if (lpb_cmd & SMP2P_RLPB_IGNORE) + return; + + switch (lpb_cmd) { + case SMP2P_LB_CMD_NOOP: + /* Do nothing */ + break; + + case SMP2P_LB_CMD_ECHO: + SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0); + SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value, + lpb_data); + (void)msm_smp2p_out_write(ctx->out, + ctx->rmt_cmd.current_value); + break; + + case SMP2P_LB_CMD_CLEARALL: + ctx->rmt_cmd.current_value = 0; + (void)msm_smp2p_out_write(ctx->out, + ctx->rmt_cmd.current_value); + break; + + case SMP2P_LB_CMD_PINGPONG: + SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0); + if (lpb_data) { + lpb_data--; + SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value, + lpb_data); + (void)msm_smp2p_out_write(ctx->out, + ctx->rmt_cmd.current_value); + } + break; + + case SMP2P_LB_CMD_RSPIN_START: + remote_spinlock_test(ctx); + break; + + case SMP2P_LB_CMD_RSPIN_LOCKED: + case SMP2P_LB_CMD_RSPIN_UNLOCKED: + case SMP2P_LB_CMD_RSPIN_END: + /* not used for remote spinlock test */ + break; + + default: + SMP2P_DBG("%s: Unknown loopback command %x\n", + __func__, lpb_cmd); + break; + } +} + +/** + * smp2p_rmt_in_edge_notify - Schedules a work item to handle the commands. + * + * @nb: Notifier block, this is called when the value in remote entry changes. + * @event: Takes value SMP2P_ENTRY_UPDATE or SMP2P_OPEN based on the event. + * @data: Consists of previous and current value in case of entry update. + * @returns: 0 for success (return value required for notifier chains). + */ +static int smp2p_rmt_in_edge_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct smp2p_loopback_ctx *ctx; + + if (!(event == SMP2P_ENTRY_UPDATE || event == SMP2P_OPEN)) + return 0; + + ctx = container_of(nb, struct smp2p_loopback_ctx, in_nb); + if (data && ctx->in_is_active) { + ctx->rmt_cmd = + *(struct msm_smp2p_update_notif *)data; + schedule_work(&ctx->rmt_lpb_work); + } + + return 0; +} + +/** + * smp2p_rmt_out_edge_notify - Notifies on the opening of the outbound entry. + * + * @nb: Notifier block, this is called when the local entry is open. + * @event: Takes on value SMP2P_OPEN when the local entry is open. + * @data: Consist of current value of the remote entry, if entry is open. + * @returns: 0 for success (return value required for notifier chains). + */ +static int smp2p_rmt_out_edge_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct smp2p_loopback_ctx *ctx; + + ctx = container_of(nb, struct smp2p_loopback_ctx, out_nb); + if (event == SMP2P_OPEN) + SMP2P_DBG("%s: 'smp2p':%d opened\n", __func__, + ctx->proc_id); + + return 0; +} + +/** + * msm_smp2p_init_rmt_lpb - Initializes the remote loopback object. + * + * @ctx: Pointer to remote loopback object that needs to be initialized. + * @pid: Processor id of the processor that is sending the commands. + * @entry: Name of the entry that needs to be opened locally. + * @returns: 0 on success, standard Linux error code otherwise. + */ +static int msm_smp2p_init_rmt_lpb(struct smp2p_loopback_ctx *ctx, + int pid, const char *entry) +{ + int ret = 0; + int tmp; + + if (!ctx || !entry || pid > SMP2P_NUM_PROCS) + return -EINVAL; + + ctx->in_nb.notifier_call = smp2p_rmt_in_edge_notify; + ctx->out_nb.notifier_call = smp2p_rmt_out_edge_notify; + ctx->proc_id = pid; + ctx->in_is_active = true; + ctx->out_is_active = true; + tmp = msm_smp2p_out_open(pid, entry, &ctx->out_nb, + &ctx->out); + if (tmp) { + SMP2P_ERR("%s: open failed outbound entry '%s':%d - ret %d\n", + __func__, entry, pid, tmp); + ret = tmp; + } + + tmp = msm_smp2p_in_register(ctx->proc_id, + SMP2P_RLPB_ENTRY_NAME, + &ctx->in_nb); + if (tmp) { + SMP2P_ERR("%s: unable to open inbound entry '%s':%d - ret %d\n", + __func__, entry, pid, tmp); + ret = tmp; + } + + return ret; +} + +/** + * msm_smp2p_init_rmt_lpb_proc - Wrapper over msm_smp2p_init_rmt_lpb + * + * @remote_pid: Processor ID of the processor that sends loopback command. + * @returns: Pointer to outbound entry handle. + */ +void *msm_smp2p_init_rmt_lpb_proc(int remote_pid) +{ + int tmp; + void *ret = NULL; + + tmp = msm_smp2p_init_rmt_lpb(&remote_loopback[remote_pid], + remote_pid, SMP2P_RLPB_ENTRY_NAME); + if (!tmp) + ret = remote_loopback[remote_pid].out; + + return ret; +} +EXPORT_SYMBOL(msm_smp2p_init_rmt_lpb_proc); + +/** + * msm_smp2p_deinit_rmt_lpb_proc - Unregister support for remote processor. + * + * @remote_pid: Processor ID of the remote system. + * @returns: 0 on success, standard Linux error code otherwise. + * + * Unregister loopback support for remote processor. + */ +int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid) +{ + int ret = 0; + int tmp; + struct smp2p_loopback_ctx *ctx; + + if (remote_pid >= SMP2P_NUM_PROCS) + return -EINVAL; + + ctx = &remote_loopback[remote_pid]; + + /* abort any pending notifications */ + remote_loopback[remote_pid].out_is_active = false; + remote_loopback[remote_pid].in_is_active = false; + flush_work(&ctx->rmt_lpb_work); + + /* unregister entries */ + tmp = msm_smp2p_out_close(&remote_loopback[remote_pid].out); + remote_loopback[remote_pid].out = NULL; + if (tmp) { + SMP2P_ERR("%s: outbound 'smp2p':%d close failed %d\n", + __func__, remote_pid, tmp); + ret = tmp; + } + + tmp = msm_smp2p_in_unregister(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &remote_loopback[remote_pid].in_nb); + if (tmp) { + SMP2P_ERR("%s: inbound 'smp2p':%d close failed %d\n", + __func__, remote_pid, tmp); + ret = tmp; + } + + return ret; +} +EXPORT_SYMBOL(msm_smp2p_deinit_rmt_lpb_proc); + +/** + * msm_smp2p_set_remote_mock_exists - Sets the remote mock configuration. + * + * @item_exists: true = Remote mock SMEM item exists + * + * This is used in the testing environment to simulate the existence of the + * remote smem item in order to test the negotiation algorithm. + */ +void msm_smp2p_set_remote_mock_exists(bool item_exists) +{ + remote_mock.item_exists = item_exists; +} +EXPORT_SYMBOL(msm_smp2p_set_remote_mock_exists); + +/** + * msm_smp2p_get_remote_mock - Get remote mock object. + * + * @returns: Point to the remote mock object. + */ +void *msm_smp2p_get_remote_mock(void) +{ + return &remote_mock; +} +EXPORT_SYMBOL(msm_smp2p_get_remote_mock); + +/** + * msm_smp2p_get_remote_mock_smem_item - Returns a pointer to remote item. + * + * @size: Size of item. + * @returns: Pointer to mock remote smem item. + */ +void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size) +{ + void *ptr = NULL; + if (remote_mock.item_exists) { + *size = sizeof(remote_mock.remote_item); + ptr = &(remote_mock.remote_item); + } + + return ptr; +} +EXPORT_SYMBOL(msm_smp2p_get_remote_mock_smem_item); + +/** + * smp2p_remote_mock_rx_interrupt - Triggers receive interrupt for mock proc. + * + * @returns: 0 for success + * + * This function simulates the receiving of interrupt by the mock remote + * processor in a testing environment. + */ +int smp2p_remote_mock_rx_interrupt(void) +{ + remote_mock.rx_interrupt_count++; + if (remote_mock.initialized) + complete(&remote_mock.cb_completion); + return 0; +} +EXPORT_SYMBOL(smp2p_remote_mock_rx_interrupt); + +/** + * smp2p_remote_mock_tx_interrupt - Calls the SMP2P interrupt handler. + * + * This function calls the interrupt handler of the Apps processor to simulate + * receiving interrupts from a remote processor. + */ +static void smp2p_remote_mock_tx_interrupt(void) +{ + msm_smp2p_interrupt_handler(SMP2P_REMOTE_MOCK_PROC); +} + +/** + * smp2p_remote_mock_init - Initialize the remote mock and loopback objects. + * + * @returns: 0 for success + */ +static int __init smp2p_remote_mock_init(void) +{ + int i; + struct smp2p_interrupt_config *int_cfg; + + smp2p_init_header(&remote_mock.remote_item.header, + SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC, + 0, 0); + remote_mock.rx_interrupt_count = 0; + remote_mock.rx_interrupt = smp2p_remote_mock_rx_interrupt; + remote_mock.tx_interrupt = smp2p_remote_mock_tx_interrupt; + remote_mock.item_exists = false; + init_completion(&remote_mock.cb_completion); + remote_mock.initialized = true; + + for (i = 0; i < SMP2P_NUM_PROCS; i++) { + INIT_WORK(&(remote_loopback[i].rmt_lpb_work), + smp2p_rmt_lpb_worker); + if (i == SMP2P_REMOTE_MOCK_PROC) + /* do not register loopback for remote mock proc */ + continue; + + int_cfg = smp2p_get_interrupt_config(); + if (!int_cfg) { + SMP2P_ERR("Remote processor config unavailable\n"); + return 0; + } + if (!int_cfg[i].is_configured) + continue; + + msm_smp2p_init_rmt_lpb(&remote_loopback[i], + i, SMP2P_RLPB_ENTRY_NAME); + } + return 0; +} +module_init(smp2p_remote_mock_init); diff --git a/drivers/soc/qcom/smp2p_private.h b/drivers/soc/qcom/smp2p_private.h new file mode 100644 index 000000000000..57f23e86272f --- /dev/null +++ b/drivers/soc/qcom/smp2p_private.h @@ -0,0 +1,253 @@ +/* drivers/soc/qcom/smp2p_private.h + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _ARCH_ARM_MACH_MSM_MSM_SMP2P_PRIVATE_H_ +#define _ARCH_ARM_MACH_MSM_MSM_SMP2P_PRIVATE_H_ + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/ipc_logging.h> +#include "smp2p_private_api.h" + +#define SMP2P_MAX_ENTRY 16 +#define SMP2P_FEATURE_SSR_ACK 0x1 + +/* SMEM Item Header Macros */ +#define SMP2P_MAGIC 0x504D5324 +#define SMP2P_LOCAL_PID_MASK 0x0000ffff +#define SMP2P_LOCAL_PID_BIT 0 +#define SMP2P_REMOTE_PID_MASK 0xffff0000 +#define SMP2P_REMOTE_PID_BIT 16 +#define SMP2P_VERSION_MASK 0x000000ff +#define SMP2P_VERSION_BIT 0 +#define SMP2P_FEATURE_MASK 0xffffff00 +#define SMP2P_FEATURE_BIT 8 +#define SMP2P_ENT_TOTAL_MASK 0x0000ffff +#define SMP2P_ENT_TOTAL_BIT 0 +#define SMP2P_ENT_VALID_MASK 0xffff0000 +#define SMP2P_ENT_VALID_BIT 16 +#define SMP2P_FLAGS_RESTART_DONE_BIT 0 +#define SMP2P_FLAGS_RESTART_DONE_MASK 0x1 +#define SMP2P_FLAGS_RESTART_ACK_BIT 1 +#define SMP2P_FLAGS_RESTART_ACK_MASK 0x2 +#define SMP2P_GPIO_NO_INT BIT(1) + +#define SMP2P_GET_BITS(hdr_val, mask, bit) \ + (((hdr_val) & (mask)) >> (bit)) +#define SMP2P_SET_BITS(hdr_val, mask, bit, new_value) \ + {\ + hdr_val = (hdr_val & ~(mask)) \ + | (((new_value) << (bit)) & (mask)); \ + } + +#define SMP2P_GET_LOCAL_PID(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_LOCAL_PID_MASK, SMP2P_LOCAL_PID_BIT) +#define SMP2P_SET_LOCAL_PID(hdr, pid) \ + SMP2P_SET_BITS(hdr, SMP2P_LOCAL_PID_MASK, SMP2P_LOCAL_PID_BIT, pid) + +#define SMP2P_GET_REMOTE_PID(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_REMOTE_PID_MASK, SMP2P_REMOTE_PID_BIT) +#define SMP2P_SET_REMOTE_PID(hdr, pid) \ + SMP2P_SET_BITS(hdr, SMP2P_REMOTE_PID_MASK, SMP2P_REMOTE_PID_BIT, pid) + +#define SMP2P_GET_VERSION(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_VERSION_MASK, SMP2P_VERSION_BIT) +#define SMP2P_SET_VERSION(hdr, version) \ + SMP2P_SET_BITS(hdr, SMP2P_VERSION_MASK, SMP2P_VERSION_BIT, version) + +#define SMP2P_GET_FEATURES(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_FEATURE_MASK, SMP2P_FEATURE_BIT) +#define SMP2P_SET_FEATURES(hdr, features) \ + SMP2P_SET_BITS(hdr, SMP2P_FEATURE_MASK, SMP2P_FEATURE_BIT, features) + +#define SMP2P_GET_ENT_TOTAL(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_ENT_TOTAL_MASK, SMP2P_ENT_TOTAL_BIT) +#define SMP2P_SET_ENT_TOTAL(hdr, entries) \ + SMP2P_SET_BITS(hdr, SMP2P_ENT_TOTAL_MASK, SMP2P_ENT_TOTAL_BIT, entries) + +#define SMP2P_GET_ENT_VALID(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_ENT_VALID_MASK, SMP2P_ENT_VALID_BIT) +#define SMP2P_SET_ENT_VALID(hdr, entries) \ + SMP2P_SET_BITS(hdr, SMP2P_ENT_VALID_MASK, SMP2P_ENT_VALID_BIT,\ + entries) + +#define SMP2P_GET_RESTART_DONE(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_FLAGS_RESTART_DONE_MASK, \ + SMP2P_FLAGS_RESTART_DONE_BIT) +#define SMP2P_SET_RESTART_DONE(hdr, value) \ + SMP2P_SET_BITS(hdr, SMP2P_FLAGS_RESTART_DONE_MASK, \ + SMP2P_FLAGS_RESTART_DONE_BIT, value) + +#define SMP2P_GET_RESTART_ACK(hdr) \ + SMP2P_GET_BITS(hdr, SMP2P_FLAGS_RESTART_ACK_MASK, \ + SMP2P_FLAGS_RESTART_ACK_BIT) +#define SMP2P_SET_RESTART_ACK(hdr, value) \ + SMP2P_SET_BITS(hdr, SMP2P_FLAGS_RESTART_ACK_MASK, \ + SMP2P_FLAGS_RESTART_ACK_BIT, value) + +/* Loopback Command Macros */ +#define SMP2P_RMT_CMD_TYPE_MASK 0x80000000 +#define SMP2P_RMT_CMD_TYPE_BIT 31 +#define SMP2P_RMT_IGNORE_MASK 0x40000000 +#define SMP2P_RMT_IGNORE_BIT 30 +#define SMP2P_RMT_CMD_MASK 0x3f000000 +#define SMP2P_RMT_CMD_BIT 24 +#define SMP2P_RMT_DATA_MASK 0x00ffffff +#define SMP2P_RMT_DATA_BIT 0 + +#define SMP2P_GET_RMT_CMD_TYPE(val) \ + SMP2P_GET_BITS(val, SMP2P_RMT_CMD_TYPE_MASK, SMP2P_RMT_CMD_TYPE_BIT) +#define SMP2P_GET_RMT_CMD(val) \ + SMP2P_GET_BITS(val, SMP2P_RMT_CMD_MASK, SMP2P_RMT_CMD_BIT) + +#define SMP2P_GET_RMT_DATA(val) \ + SMP2P_GET_BITS(val, SMP2P_RMT_DATA_MASK, SMP2P_RMT_DATA_BIT) + +#define SMP2P_SET_RMT_CMD_TYPE(val, cmd_type) \ + SMP2P_SET_BITS(val, SMP2P_RMT_CMD_TYPE_MASK, SMP2P_RMT_CMD_TYPE_BIT, \ + cmd_type) +#define SMP2P_SET_RMT_CMD_TYPE_REQ(val) \ + SMP2P_SET_RMT_CMD_TYPE(val, 1) +#define SMP2P_SET_RMT_CMD_TYPE_RESP(val) \ + SMP2P_SET_RMT_CMD_TYPE(val, 0) + +#define SMP2P_SET_RMT_CMD(val, cmd) \ + SMP2P_SET_BITS(val, SMP2P_RMT_CMD_MASK, SMP2P_RMT_CMD_BIT, \ + cmd) +#define SMP2P_SET_RMT_DATA(val, data) \ + SMP2P_SET_BITS(val, SMP2P_RMT_DATA_MASK, SMP2P_RMT_DATA_BIT, data) + +enum { + SMP2P_LB_CMD_NOOP = 0x0, + SMP2P_LB_CMD_ECHO, + SMP2P_LB_CMD_CLEARALL, + SMP2P_LB_CMD_PINGPONG, + SMP2P_LB_CMD_RSPIN_START, + SMP2P_LB_CMD_RSPIN_LOCKED, + SMP2P_LB_CMD_RSPIN_UNLOCKED, + SMP2P_LB_CMD_RSPIN_END, +}; +#define SMP2P_RLPB_IGNORE 0x40 +#define SMP2P_RLPB_ENTRY_NAME "smp2p" + +/* Debug Logging Macros */ +enum { + MSM_SMP2P_INFO = 1U << 0, + MSM_SMP2P_DEBUG = 1U << 1, + MSM_SMP2P_GPIO = 1U << 2, +}; + +#define SMP2P_IPC_LOG_STR(x...) do { \ + if (smp2p_get_log_ctx()) \ + ipc_log_string(smp2p_get_log_ctx(), x); \ +} while (0) + +#define SMP2P_DBG(x...) do { \ + if (smp2p_get_debug_mask() & MSM_SMP2P_DEBUG) \ + SMP2P_IPC_LOG_STR(x); \ +} while (0) + +#define SMP2P_INFO(x...) do { \ + if (smp2p_get_debug_mask() & MSM_SMP2P_INFO) \ + SMP2P_IPC_LOG_STR(x); \ +} while (0) + +#define SMP2P_ERR(x...) do { \ + pr_err(x); \ + SMP2P_IPC_LOG_STR(x); \ +} while (0) + +#define SMP2P_GPIO(x...) do { \ + if (smp2p_get_debug_mask() & MSM_SMP2P_GPIO) \ + SMP2P_IPC_LOG_STR(x); \ +} while (0) + + +enum msm_smp2p_edge_state { + SMP2P_EDGE_STATE_CLOSED, + SMP2P_EDGE_STATE_OPENING, + SMP2P_EDGE_STATE_OPENED, + SMP2P_EDGE_STATE_FAILED = 0xff, +}; + +/** + * struct smp2p_smem - SMP2P SMEM Item Header + * + * @magic: Set to "$SMP" -- used for identification / debug purposes + * @feature_version: Feature and version fields + * @rem_loc_proc_id: Remote (31:16) and Local (15:0) processor IDs + * @valid_total_ent: Valid (31:16) and total (15:0) entries + * @flags: Flags (bits 31:2 reserved) + */ +struct smp2p_smem { + uint32_t magic; + uint32_t feature_version; + uint32_t rem_loc_proc_id; + uint32_t valid_total_ent; + uint32_t flags; +}; + +struct smp2p_entry_v1 { + char name[SMP2P_MAX_ENTRY_NAME]; + uint32_t entry; +}; + +struct smp2p_smem_item { + struct smp2p_smem header; + struct smp2p_entry_v1 entries[SMP2P_MAX_ENTRY]; +}; + +/* Mock object for internal loopback testing. */ +struct msm_smp2p_remote_mock { + struct smp2p_smem_item remote_item; + int rx_interrupt_count; + int (*rx_interrupt)(void); + void (*tx_interrupt)(void); + + bool item_exists; + bool initialized; + struct completion cb_completion; +}; + +void smp2p_init_header(struct smp2p_smem *header_ptr, int local_pid, + int remote_pid, uint32_t features, uint32_t version); +void *msm_smp2p_get_remote_mock(void); +int smp2p_remote_mock_rx_interrupt(void); +int smp2p_reset_mock_edge(void); +void msm_smp2p_interrupt_handler(int); +void msm_smp2p_set_remote_mock_exists(bool item_exists); +void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size); +void *msm_smp2p_init_rmt_lpb_proc(int remote_pid); +int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid); +void *smp2p_get_log_ctx(void); +int smp2p_get_debug_mask(void); + +/* Inbound / outbound Interrupt configuration. */ +struct smp2p_interrupt_config { + bool is_configured; + uint32_t *out_int_ptr; + uint32_t out_int_mask; + int in_int_id; + const char *name; + + /* interrupt stats */ + unsigned in_interrupt_count; + unsigned out_interrupt_count; +}; + +struct smp2p_interrupt_config *smp2p_get_interrupt_config(void); +const char *smp2p_pid_to_name(int remote_pid); +struct smp2p_smem *smp2p_get_in_item(int remote_pid); +struct smp2p_smem *smp2p_get_out_item(int remote_pid, int *state); +void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open); +#endif diff --git a/drivers/soc/qcom/smp2p_private_api.h b/drivers/soc/qcom/smp2p_private_api.h new file mode 100644 index 000000000000..4223a2e1bc90 --- /dev/null +++ b/drivers/soc/qcom/smp2p_private_api.h @@ -0,0 +1,80 @@ +/* drivers/soc/qcom/smp2p_private_api.h + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_ +#define _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_ + +#include <linux/notifier.h> + +struct msm_smp2p_out; + +/* Maximum size of the entry name and trailing null. */ +#define SMP2P_MAX_ENTRY_NAME 16 + +/* Bits per entry */ +#define SMP2P_BITS_PER_ENTRY 32 + +/* Processor ID's */ +enum { + SMP2P_APPS_PROC = 0, + SMP2P_MODEM_PROC = 1, + SMP2P_AUDIO_PROC = 2, + SMP2P_SENSOR_PROC = 3, + SMP2P_WIRELESS_PROC = 4, + SMP2P_RESERVED_PROC_2 = 5, + SMP2P_POWER_PROC = 6, + SMP2P_TZ_PROC = 7, + /* add new processors here */ + + SMP2P_REMOTE_MOCK_PROC = 15, + SMP2P_NUM_PROCS, +}; + +/** + * Notification events that are passed to notifier for incoming and outgoing + * entries. + * + * If the @metadata argument in the notifier is non-null, then it will + * point to the associated struct smux_meta_* structure. + */ +enum msm_smp2p_events { + SMP2P_OPEN, /* data is NULL */ + SMP2P_ENTRY_UPDATE, /* data => struct msm_smp2p_update_notif */ +}; + +/** + * Passed in response to a SMP2P_ENTRY_UPDATE event. + * + * @prev_value: previous value of entry + * @current_value: latest value of entry + */ +struct msm_smp2p_update_notif { + uint32_t previous_value; + uint32_t current_value; +}; + +int msm_smp2p_out_open(int remote_pid, const char *entry, + struct notifier_block *open_notifier, + struct msm_smp2p_out **handle); +int msm_smp2p_out_close(struct msm_smp2p_out **handle); +int msm_smp2p_out_read(struct msm_smp2p_out *handle, uint32_t *data); +int msm_smp2p_out_write(struct msm_smp2p_out *handle, uint32_t data); +int msm_smp2p_out_modify(struct msm_smp2p_out *handle, uint32_t set_mask, + uint32_t clear_mask, bool send_irq); +int msm_smp2p_in_read(int remote_pid, const char *entry, uint32_t *data); +int msm_smp2p_in_register(int remote_pid, const char *entry, + struct notifier_block *in_notifier); +int msm_smp2p_in_unregister(int remote_pid, const char *entry, + struct notifier_block *in_notifier); + +#endif /* _ARCH_ARM_MACH_MSM_SMP2P_PRIVATE_API_H_ */ diff --git a/drivers/soc/qcom/smp2p_sleepstate.c b/drivers/soc/qcom/smp2p_sleepstate.c new file mode 100644 index 000000000000..dfebd201eeb0 --- /dev/null +++ b/drivers/soc/qcom/smp2p_sleepstate.c @@ -0,0 +1,104 @@ +/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include "smp2p_private.h" + +#define SET_DELAY (2 * HZ) +#define PROC_AWAKE_ID 12 /* 12th bit */ +static int slst_gpio_base_id; + +/** + * sleepstate_pm_notifier() - PM notifier callback function. + * @nb: Pointer to the notifier block. + * @event: Suspend state event from PM module. + * @unused: Null pointer from PM module. + * + * This function is register as callback function to get notifications + * from the PM module on the system suspend state. + */ +static int sleepstate_pm_notifier(struct notifier_block *nb, + unsigned long event, void *unused) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + gpio_set_value(slst_gpio_base_id + PROC_AWAKE_ID, 0); + break; + + case PM_POST_SUSPEND: + gpio_set_value(slst_gpio_base_id + PROC_AWAKE_ID, 1); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block sleepstate_pm_nb = { + .notifier_call = sleepstate_pm_notifier, +}; + +static int smp2p_sleepstate_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *node = pdev->dev.of_node; + + slst_gpio_base_id = of_get_gpio(node, 0); + if (slst_gpio_base_id == -EPROBE_DEFER) { + return slst_gpio_base_id; + } else if (slst_gpio_base_id < 0) { + SMP2P_ERR("%s: Error to get gpio %d\n", + __func__, slst_gpio_base_id); + return slst_gpio_base_id; + } + + + gpio_set_value(slst_gpio_base_id + PROC_AWAKE_ID, 1); + + ret = register_pm_notifier(&sleepstate_pm_nb); + if (ret) + SMP2P_ERR("%s: power state notif error %d\n", __func__, ret); + + return 0; +} + +static struct of_device_id msm_smp2p_slst_match_table[] = { + {.compatible = "qcom,smp2pgpio_sleepstate_3_out"}, + {}, +}; + +static struct platform_driver smp2p_sleepstate_driver = { + .probe = smp2p_sleepstate_probe, + .driver = { + .name = "smp2p_sleepstate", + .owner = THIS_MODULE, + .of_match_table = msm_smp2p_slst_match_table, + }, +}; + +static int __init smp2p_sleepstate_init(void) +{ + int ret; + ret = platform_driver_register(&smp2p_sleepstate_driver); + if (ret) { + SMP2P_ERR("%s: smp2p_sleepstate_driver register failed %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +module_init(smp2p_sleepstate_init); +MODULE_DESCRIPTION("SMP2P SLEEP STATE"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/smp2p_spinlock_test.c b/drivers/soc/qcom/smp2p_spinlock_test.c new file mode 100644 index 000000000000..99b4558ef01f --- /dev/null +++ b/drivers/soc/qcom/smp2p_spinlock_test.c @@ -0,0 +1,804 @@ +/* drivers/soc/qcom/smp2p_spinlock_test.c + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/remote_spinlock.h> +#include <soc/qcom/smem.h> +#include "smem_private.h" +#include "smp2p_private.h" +#include "smp2p_test_common.h" + +#define RS_END_THIEF_PID_BIT 20 +#define RS_END_THIEF_MASK 0x00f00000 + +/* Spinlock commands used for testing Apps<->RPM spinlocks. */ +enum RPM_SPINLOCK_CMDS { + RPM_CMD_INVALID, + RPM_CMD_START, + RPM_CMD_LOCKED, + RPM_CMD_UNLOCKED, + RPM_CMD_END, +}; + +/* Shared structure for testing Apps<->RPM spinlocks. */ +struct rpm_spinlock_test { + uint32_t apps_cmd; + uint32_t apps_lock_count; + uint32_t rpm_cmd; + uint32_t rpm_lock_count; +}; + +static uint32_t ut_remote_spinlock_run_time = 1; + +/** + * smp2p_ut_remote_spinlock_core - Verify remote spinlock. + * + * @s: Pointer to output file + * @remote_pid: Remote processor to test + * @use_trylock: Use trylock to prevent an Apps deadlock if the + * remote spinlock fails. + */ +static void smp2p_ut_remote_spinlock_core(struct seq_file *s, int remote_pid, + bool use_trylock) +{ + int failed = 0; + unsigned lock_count = 0; + struct msm_smp2p_out *handle = NULL; + int ret; + uint32_t test_request; + uint32_t test_response; + struct mock_cb_data cb_out; + struct mock_cb_data cb_in; + unsigned long flags; + unsigned n; + bool have_lock; + bool timeout; + int failed_tmp; + int spinlock_owner; + remote_spinlock_t *smem_spinlock; + unsigned long end; + + seq_printf(s, "Running %s for '%s' remote pid %d\n", + __func__, smp2p_pid_to_name(remote_pid), remote_pid); + + cb_out.initialized = false; + cb_in.initialized = false; + mock_cb_data_init(&cb_out); + mock_cb_data_init(&cb_in); + do { + smem_spinlock = smem_get_remote_spinlock(); + UT_ASSERT_PTR(smem_spinlock, !=, NULL); + + /* Open output entry */ + ret = msm_smp2p_out_open(remote_pid, SMP2P_RLPB_ENTRY_NAME, + &cb_out.nb, &handle); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_out.cb_completion, HZ * 2), + >, 0); + UT_ASSERT_INT(cb_out.cb_count, ==, 1); + UT_ASSERT_INT(cb_out.event_open, ==, 1); + + /* Open inbound entry */ + ret = msm_smp2p_in_register(remote_pid, SMP2P_RLPB_ENTRY_NAME, + &cb_in.nb); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_in.cb_completion, HZ * 2), + >, 0); + UT_ASSERT_INT(cb_in.cb_count, ==, 1); + UT_ASSERT_INT(cb_in.event_open, ==, 1); + + /* Send start */ + mock_cb_data_reset(&cb_in); + mock_cb_data_reset(&cb_out); + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE_REQ(test_request); + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_START); + SMP2P_SET_RMT_DATA(test_request, 0x0); + ret = msm_smp2p_out_write(handle, test_request); + UT_ASSERT_INT(ret, ==, 0); + + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_in.cb_completion, HZ * 2), + >, 0); + UT_ASSERT_INT(cb_in.cb_count, ==, 1); + UT_ASSERT_INT(cb_in.event_entry_update, ==, 1); + ret = msm_smp2p_in_read(remote_pid, SMP2P_RLPB_ENTRY_NAME, + &test_response); + UT_ASSERT_INT(ret, ==, 0); + + test_response = SMP2P_GET_RMT_CMD(test_response); + if (test_response != SMP2P_LB_CMD_RSPIN_LOCKED && + test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED) { + /* invalid response from remote - abort test */ + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE(test_request, 1); + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, 0x0); + ret = msm_smp2p_out_write(handle, test_request); + UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_LOCKED, ==, + test_response); + } + + /* Run spinlock test */ + if (use_trylock) + seq_puts(s, "\tUsing remote_spin_trylock\n"); + else + seq_puts(s, "\tUsing remote_spin_lock\n"); + + flags = 0; + have_lock = false; + timeout = false; + spinlock_owner = 0; + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE_REQ(test_request); + end = jiffies + (ut_remote_spinlock_run_time * HZ); + if (ut_remote_spinlock_run_time < 300) { + seq_printf(s, "\tRunning test for %u seconds; ", + ut_remote_spinlock_run_time); + seq_puts(s, + "on physical hardware please run >= 300 seconds by doing 'echo 300 > ut_remote_spinlock_time'\n"); + } + while (time_is_after_jiffies(end)) { + /* try to acquire spinlock */ + if (use_trylock) { + unsigned long j_start = jiffies; + while (!remote_spin_trylock_irqsave( + smem_spinlock, flags)) { + if (jiffies_to_msecs(jiffies - j_start) + > 1000) { + seq_puts(s, + "\tFail: Timeout trying to get the lock\n"); + timeout = true; + break; + } + } + if (timeout) + break; + } else { + remote_spin_lock_irqsave(smem_spinlock, flags); + } + have_lock = true; + ++lock_count; + + /* tell the remote side that we have the lock */ + SMP2P_SET_RMT_DATA(test_request, lock_count); + SMP2P_SET_RMT_CMD(test_request, + SMP2P_LB_CMD_RSPIN_LOCKED); + ret = msm_smp2p_out_write(handle, test_request); + UT_ASSERT_INT(ret, ==, 0); + + /* verify the other side doesn't say it has the lock */ + for (n = 0; n < 1000; ++n) { + spinlock_owner = + remote_spin_owner(smem_spinlock); + if (spinlock_owner != SMEM_APPS) { + /* lock stolen by remote side */ + seq_puts(s, "\tFail: Remote side: "); + seq_printf(s, "%d stole lock pid: %d\n", + remote_pid, spinlock_owner); + failed = true; + break; + } + spinlock_owner = 0; + + ret = msm_smp2p_in_read(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &test_response); + UT_ASSERT_INT(ret, ==, 0); + test_response = + SMP2P_GET_RMT_CMD(test_response); + UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_UNLOCKED, ==, + test_response); + } + if (failed) + break; + + /* tell remote side we are unlocked and release lock */ + SMP2P_SET_RMT_CMD(test_request, + SMP2P_LB_CMD_RSPIN_UNLOCKED); + (void)msm_smp2p_out_write(handle, test_request); + have_lock = false; + remote_spin_unlock_irqrestore(smem_spinlock, flags); + } + if (have_lock) + remote_spin_unlock_irqrestore(smem_spinlock, flags); + + /* End test */ + mock_cb_data_reset(&cb_in); + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, lock_count | + (spinlock_owner << RS_END_THIEF_PID_BIT)); + (void)msm_smp2p_out_write(handle, test_request); + + failed_tmp = failed; + failed = false; + do { + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_in.cb_completion, HZ * 2), + >, 0); + reinit_completion(&cb_in.cb_completion); + ret = msm_smp2p_in_read(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &test_response); + UT_ASSERT_INT(ret, ==, 0); + } while (!failed && + SMP2P_GET_RMT_CMD(test_response) != + SMP2P_LB_CMD_RSPIN_END); + if (failed) + break; + failed = failed_tmp; + + test_response = SMP2P_GET_RMT_DATA(test_response); + seq_puts(s, "\tLocked spinlock "); + seq_printf(s, "local %u times; remote %u times", + lock_count, + test_response & ((1 << RS_END_THIEF_PID_BIT) - 1) + ); + if (test_response & RS_END_THIEF_MASK) { + seq_puts(s, "Remote side reporting lock stolen by "); + seq_printf(s, "pid %d.\n", + SMP2P_GET_BITS(test_response, + RS_END_THIEF_MASK, + RS_END_THIEF_PID_BIT)); + failed = 1; + } + seq_puts(s, "\n"); + + /* Cleanup */ + ret = msm_smp2p_out_close(&handle); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_PTR(handle, ==, NULL); + ret = msm_smp2p_in_unregister(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &cb_in.nb); + UT_ASSERT_INT(ret, ==, 0); + + if (!failed && !timeout) + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + if (handle) { + /* send end command */ + test_request = 0; + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, lock_count); + (void)msm_smp2p_out_write(handle, test_request); + (void)msm_smp2p_out_close(&handle); + } + (void)msm_smp2p_in_unregister(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &cb_in.nb); + + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + } +} + +/** + * smp2p_ut_remote_spinlock_pid - Verify remote spinlock for a processor. + * + * @s: Pointer to output file + * @pid: Processor to test + * @use_trylock: Use trylock to prevent an Apps deadlock if the + * remote spinlock fails. + */ +static void smp2p_ut_remote_spinlock_pid(struct seq_file *s, int pid, + bool use_trylock) +{ + struct smp2p_interrupt_config *int_cfg; + + int_cfg = smp2p_get_interrupt_config(); + if (!int_cfg) { + seq_puts(s, "Remote processor config unavailable\n"); + return; + } + + if (pid >= SMP2P_NUM_PROCS || !int_cfg[pid].is_configured) + return; + + msm_smp2p_deinit_rmt_lpb_proc(pid); + smp2p_ut_remote_spinlock_core(s, pid, use_trylock); + msm_smp2p_init_rmt_lpb_proc(pid); +} + +/** + * smp2p_ut_remote_spinlock - Verify remote spinlock for all processors. + * + * @s: pointer to output file + */ +static void smp2p_ut_remote_spinlock(struct seq_file *s) +{ + int pid; + + for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) + smp2p_ut_remote_spinlock_pid(s, pid, false); +} + +/** + * smp2p_ut_remote_spin_trylock - Verify remote trylock for all processors. + * + * @s: Pointer to output file + */ +static void smp2p_ut_remote_spin_trylock(struct seq_file *s) +{ + int pid; + + for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) + smp2p_ut_remote_spinlock_pid(s, pid, true); +} + +/** + * smp2p_ut_remote_spinlock - Verify remote spinlock for all processors. + * + * @s: pointer to output file + * + * This test verifies inbound and outbound functionality for all + * configured remote processor. + */ +static void smp2p_ut_remote_spinlock_modem(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_pid(s, SMP2P_MODEM_PROC, false); +} + +static void smp2p_ut_remote_spinlock_adsp(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_pid(s, SMP2P_AUDIO_PROC, false); +} + +static void smp2p_ut_remote_spinlock_dsps(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_pid(s, SMP2P_SENSOR_PROC, false); +} + +static void smp2p_ut_remote_spinlock_wcnss(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_pid(s, SMP2P_WIRELESS_PROC, false); +} + +static void smp2p_ut_remote_spinlock_tz(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_pid(s, SMP2P_TZ_PROC, false); +} + +/** + * smp2p_ut_remote_spinlock_rpm - Verify remote spinlock. + * + * @s: pointer to output file + * @remote_pid: Remote processor to test + */ +static void smp2p_ut_remote_spinlock_rpm(struct seq_file *s) +{ + int failed = 0; + unsigned long flags; + unsigned n; + unsigned test_num; + struct rpm_spinlock_test *data_ptr; + remote_spinlock_t *smem_spinlock; + bool have_lock; + + seq_printf(s, "Running %s for Apps<->RPM Test\n", + __func__); + do { + smem_spinlock = smem_get_remote_spinlock(); + UT_ASSERT_PTR(smem_spinlock, !=, NULL); + + data_ptr = smem_alloc(SMEM_ID_VENDOR0, + sizeof(struct rpm_spinlock_test), 0, + SMEM_ANY_HOST_FLAG); + UT_ASSERT_PTR(0, !=, data_ptr); + + /* Send start */ + writel_relaxed(0, &data_ptr->apps_lock_count); + writel_relaxed(RPM_CMD_START, &data_ptr->apps_cmd); + + seq_puts(s, "\tWaiting for RPM to start test\n"); + for (n = 0; n < 1000; ++n) { + if (readl_relaxed(&data_ptr->rpm_cmd) != + RPM_CMD_INVALID) + break; + usleep_range(1000, 1200); + } + if (readl_relaxed(&data_ptr->rpm_cmd) == RPM_CMD_INVALID) { + /* timeout waiting for RPM */ + writel_relaxed(RPM_CMD_INVALID, &data_ptr->apps_cmd); + UT_ASSERT_INT(RPM_CMD_LOCKED, !=, RPM_CMD_INVALID); + } + + /* Run spinlock test */ + flags = 0; + have_lock = false; + for (test_num = 0; !failed && test_num < 10000; ++test_num) { + /* acquire spinlock */ + remote_spin_lock_irqsave(smem_spinlock, flags); + have_lock = true; + data_ptr->apps_lock_count++; + writel_relaxed(data_ptr->apps_lock_count, + &data_ptr->apps_lock_count); + writel_relaxed(RPM_CMD_LOCKED, &data_ptr->apps_cmd); + /* + * Ensure that the remote side sees our lock has + * been acquired before we start polling their status. + */ + wmb(); + + /* verify the other side doesn't say it has the lock */ + for (n = 0; n < 1000; ++n) { + UT_ASSERT_HEX(RPM_CMD_UNLOCKED, ==, + readl_relaxed(&data_ptr->rpm_cmd)); + } + if (failed) + break; + + /* release spinlock */ + have_lock = false; + writel_relaxed(RPM_CMD_UNLOCKED, &data_ptr->apps_cmd); + /* + * Ensure that our status-update write was committed + * before we unlock the spinlock. + */ + wmb(); + remote_spin_unlock_irqrestore(smem_spinlock, flags); + } + if (have_lock) + remote_spin_unlock_irqrestore(smem_spinlock, flags); + + /* End test */ + writel_relaxed(RPM_CMD_INVALID, &data_ptr->apps_cmd); + seq_printf(s, "\tLocked spinlock local %u remote %u\n", + readl_relaxed(&data_ptr->apps_lock_count), + readl_relaxed(&data_ptr->rpm_lock_count)); + + if (!failed) + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + } +} + +struct rmt_spinlock_work_item { + struct work_struct work; + struct completion try_lock; + struct completion locked; + bool has_locked; +}; + +static void ut_remote_spinlock_ssr_worker(struct work_struct *work) +{ + remote_spinlock_t *smem_spinlock; + unsigned long flags; + struct rmt_spinlock_work_item *work_item = + container_of(work, struct rmt_spinlock_work_item, work); + + work_item->has_locked = false; + complete(&work_item->try_lock); + smem_spinlock = smem_get_remote_spinlock(); + if (!smem_spinlock) { + pr_err("%s Failed\n", __func__); + return; + } + + remote_spin_lock_irqsave(smem_spinlock, flags); + remote_spin_unlock_irqrestore(smem_spinlock, flags); + work_item->has_locked = true; + complete(&work_item->locked); +} + +/** + * smp2p_ut_remote_spinlock_ssr - Verify remote spinlock. + * + * @s: pointer to output file + */ +static void smp2p_ut_remote_spinlock_ssr(struct seq_file *s) +{ + int failed = 0; + unsigned long flags; + remote_spinlock_t *smem_spinlock; + int spinlock_owner = 0; + + struct workqueue_struct *ws = NULL; + struct rmt_spinlock_work_item work_item; + + seq_printf(s, " Running %s Test\n", + __func__); + do { + smem_spinlock = smem_get_remote_spinlock(); + UT_ASSERT_PTR(smem_spinlock, !=, NULL); + + ws = create_singlethread_workqueue("ut_remote_spinlock_ssr"); + UT_ASSERT_PTR(ws, !=, NULL); + INIT_WORK(&work_item.work, ut_remote_spinlock_ssr_worker); + init_completion(&work_item.try_lock); + init_completion(&work_item.locked); + + remote_spin_lock_irqsave(smem_spinlock, flags); + /* Unlock local spin lock and hold HW spinlock */ + spin_unlock_irqrestore(&((smem_spinlock)->local), flags); + + queue_work(ws, &work_item.work); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &work_item.try_lock, HZ * 2), >, 0); + UT_ASSERT_INT((int)work_item.has_locked, ==, 0); + spinlock_owner = remote_spin_owner(smem_spinlock); + UT_ASSERT_INT(spinlock_owner, ==, SMEM_APPS); + remote_spin_release_all(SMEM_APPS); + + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &work_item.locked, HZ * 2), >, 0); + + if (!failed) + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + } +} + +/** + * smp2p_ut_remote_spinlock_track_core - Verify remote spinlock. + * + * @s: Pointer to output file + * @remote_pid: Remote processor to test + * + * This test has the remote subsystem grab the lock, and then has the local + * subsystem attempt to grab the lock using the trylock() API. It then verifies + * that the ID in the hw_spinlocks array matches the owner of the lock. + */ +static void smp2p_ut_remote_spinlock_track_core(struct seq_file *s, + int remote_pid) +{ + int failed = 0; + struct msm_smp2p_out *handle = NULL; + int ret; + uint32_t test_request; + uint32_t test_response; + struct mock_cb_data cb_out; + struct mock_cb_data cb_in; + unsigned long flags; + int stored_value; + remote_spinlock_t *smem_spinlock; + + seq_printf(s, "Running %s for '%s' remote pid %d\n", + __func__, smp2p_pid_to_name(remote_pid), remote_pid); + + cb_out.initialized = false; + cb_in.initialized = false; + mock_cb_data_init(&cb_out); + mock_cb_data_init(&cb_in); + do { + smem_spinlock = smem_get_remote_spinlock(); + UT_ASSERT_PTR(smem_spinlock, !=, NULL); + + /* Open output entry */ + ret = msm_smp2p_out_open(remote_pid, SMP2P_RLPB_ENTRY_NAME, + &cb_out.nb, &handle); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_out.cb_completion, HZ * 2), + >, 0); + UT_ASSERT_INT(cb_out.cb_count, ==, 1); + UT_ASSERT_INT(cb_out.event_open, ==, 1); + + /* Open inbound entry */ + ret = msm_smp2p_in_register(remote_pid, SMP2P_RLPB_ENTRY_NAME, + &cb_in.nb); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_in.cb_completion, HZ * 2), + >, 0); + UT_ASSERT_INT(cb_in.cb_count, ==, 1); + UT_ASSERT_INT(cb_in.event_open, ==, 1); + + /* Send start */ + mock_cb_data_reset(&cb_in); + mock_cb_data_reset(&cb_out); + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE_REQ(test_request); + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_START); + SMP2P_SET_RMT_DATA(test_request, 0x0); + ret = msm_smp2p_out_write(handle, test_request); + UT_ASSERT_INT(ret, ==, 0); + + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_in.cb_completion, HZ * 2), + >, 0); + UT_ASSERT_INT(cb_in.cb_count, ==, 1); + UT_ASSERT_INT(cb_in.event_entry_update, ==, 1); + ret = msm_smp2p_in_read(remote_pid, SMP2P_RLPB_ENTRY_NAME, + &test_response); + UT_ASSERT_INT(ret, ==, 0); + + test_response = SMP2P_GET_RMT_CMD(test_response); + if (test_response != SMP2P_LB_CMD_RSPIN_LOCKED && + test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED) { + /* invalid response from remote - abort test */ + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE(test_request, 1); + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, 0x0); + ret = msm_smp2p_out_write(handle, test_request); + UT_ASSERT_HEX(SMP2P_LB_CMD_RSPIN_LOCKED, ==, + test_response); + } + + /* Run spinlock test */ + flags = 0; + test_request = 0x0; + SMP2P_SET_RMT_CMD_TYPE_REQ(test_request); + + /* try to acquire spinlock */ + remote_spin_trylock_irqsave(smem_spinlock, flags); + /* + * Need to check against the locking token (PID + 1) + * because the remote_spin_owner() API only returns the + * PID. + */ + stored_value = remote_spin_get_hw_spinlocks_element( + smem_spinlock); + UT_ASSERT_INT(stored_value, ==, + remote_spin_owner(smem_spinlock) + 1); + UT_ASSERT_INT(stored_value, ==, remote_pid + 1); + + /* End test */ + test_request = 0x0; + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, 0x0); + (void)msm_smp2p_out_write(handle, test_request); + + /* Cleanup */ + ret = msm_smp2p_out_close(&handle); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_PTR(handle, ==, NULL); + ret = msm_smp2p_in_unregister(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &cb_in.nb); + UT_ASSERT_INT(ret, ==, 0); + + if (!failed) + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + if (handle) { + /* send end command */ + test_request = 0x0; + SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); + SMP2P_SET_RMT_DATA(test_request, 0x0); + (void)msm_smp2p_out_write(handle, test_request); + (void)msm_smp2p_out_close(&handle); + } + (void)msm_smp2p_in_unregister(remote_pid, + SMP2P_RLPB_ENTRY_NAME, &cb_in.nb); + + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + } +} + +/** + * smp2p_ut_remote_spinlock_track - Verify PID tracking for modem. + * + * @s: Pointer to output file + * @pid: The processor to test + */ +static void smp2p_ut_remote_spinlock_track(struct seq_file *s, int pid) +{ + struct smp2p_interrupt_config *int_cfg; + + int_cfg = smp2p_get_interrupt_config(); + if (!int_cfg) { + seq_puts(s, "Remote processor config unavailable\n"); + return; + } + + if (pid >= SMP2P_NUM_PROCS || !int_cfg[pid].is_configured) + return; + + msm_smp2p_deinit_rmt_lpb_proc(pid); + smp2p_ut_remote_spinlock_track_core(s, pid); + msm_smp2p_init_rmt_lpb_proc(pid); +} + +/** + * smp2p_ut_remote_spinlock_track - Verify PID tracking for all processors. + * + * @s: Pointer to output file + * + * This test verifies PID tracking for all configured remote processors. + */ +static void smp2p_ut_remote_spinlock_track_modem(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_track(s, SMP2P_MODEM_PROC); +} + +static void smp2p_ut_remote_spinlock_track_adsp(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_track(s, SMP2P_AUDIO_PROC); +} + +static void smp2p_ut_remote_spinlock_track_dsps(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_track(s, SMP2P_SENSOR_PROC); +} + +static void smp2p_ut_remote_spinlock_track_wcnss(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_track(s, SMP2P_WIRELESS_PROC); +} + +static void smp2p_ut_remote_spinlock_track_tz(struct seq_file *s) +{ + smp2p_ut_remote_spinlock_track(s, SMP2P_TZ_PROC); +} + +static int __init smp2p_debugfs_init(void) +{ + /* + * Add Unit Test entries. + * + * The idea with unit tests is that you can run all of them + * from ADB shell by doing: + * adb shell + * cat ut* + * + * And if particular tests fail, you can then repeatedly run the + * failing tests as you debug and resolve the failing test. + */ + smp2p_debug_create("ut_remote_spinlock", + smp2p_ut_remote_spinlock); + smp2p_debug_create("ut_remote_spin_trylock", + smp2p_ut_remote_spin_trylock); + smp2p_debug_create("ut_remote_spinlock_modem", + smp2p_ut_remote_spinlock_modem); + smp2p_debug_create("ut_remote_spinlock_adsp", + smp2p_ut_remote_spinlock_adsp); + smp2p_debug_create("ut_remote_spinlock_dsps", + smp2p_ut_remote_spinlock_dsps); + smp2p_debug_create("ut_remote_spinlock_wcnss", + smp2p_ut_remote_spinlock_wcnss); + smp2p_debug_create("ut_remote_spinlock_tz", + smp2p_ut_remote_spinlock_tz); + smp2p_debug_create("ut_remote_spinlock_rpm", + smp2p_ut_remote_spinlock_rpm); + smp2p_debug_create_u32("ut_remote_spinlock_time", + &ut_remote_spinlock_run_time); + smp2p_debug_create("ut_remote_spinlock_ssr", + &smp2p_ut_remote_spinlock_ssr); + smp2p_debug_create("ut_remote_spinlock_track_modem", + &smp2p_ut_remote_spinlock_track_modem); + smp2p_debug_create("ut_remote_spinlock_track_adsp", + &smp2p_ut_remote_spinlock_track_adsp); + smp2p_debug_create("ut_remote_spinlock_track_dsps", + &smp2p_ut_remote_spinlock_track_dsps); + smp2p_debug_create("ut_remote_spinlock_track_wcnss", + &smp2p_ut_remote_spinlock_track_wcnss); + smp2p_debug_create("ut_remote_spinlock_track_tz", + &smp2p_ut_remote_spinlock_track_tz); + return 0; +} +module_init(smp2p_debugfs_init); diff --git a/drivers/soc/qcom/smp2p_test.c b/drivers/soc/qcom/smp2p_test.c new file mode 100644 index 000000000000..20f00b2ebf1e --- /dev/null +++ b/drivers/soc/qcom/smp2p_test.c @@ -0,0 +1,1322 @@ +/* drivers/soc/qcom/smp2p_test.c + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <soc/qcom/subsystem_restart.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.flags = 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_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(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.flags = 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_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(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.flags = 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_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(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.flags = 0x0; + msm_smp2p_set_remote_mock_exists(true); + + /* Create test entry and attach loopback server */ + rmp->rx_interrupt_count = 0; + reinit_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; + reinit_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; + reinit_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; + reinit_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_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(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_puts(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_puts(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_puts(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_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(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_puts(s, "Remote processor config unavailable\n"); + return; + } + + for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) { + if (!int_cfg[pid].is_configured) + continue; + + smp2p_ut_remote_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.flags = 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_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(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)); + } +} + +/** + * smp2p_ut_local_in_multiple - Verify Multiple Inbound Registration. + * + * @s: pointer to output file + * + * This test verifies multiple clients registering for same inbound entries + * using the remote mock processor. + */ +static void smp2p_ut_local_in_multiple(struct seq_file *s) +{ + int failed = 0; + struct msm_smp2p_remote_mock *rmp = NULL; + int ret; + static struct mock_cb_data cb_in_1; + static struct mock_cb_data cb_in_2; + static struct mock_cb_data cb_out; + + seq_printf(s, "Running %s\n", __func__); + + mock_cb_data_init(&cb_in_1); + mock_cb_data_init(&cb_in_2); + 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, 1); + SMP2P_SET_ENT_VALID( + rmp->remote_item.header.valid_total_ent, 0); + rmp->remote_item.header.flags = 0x0; + msm_smp2p_set_remote_mock_exists(true); + + /* Create an Entry in the remote mock object */ + scnprintf(rmp->remote_item.entries[0].name, + SMP2P_MAX_ENTRY_NAME, "smp2p%d", 1); + rmp->remote_item.entries[0].entry = 0; + rmp->tx_interrupt(); + + /* Register multiple clients for the inbound entry */ + ret = msm_smp2p_in_register(SMP2P_REMOTE_MOCK_PROC, + rmp->remote_item.entries[0].name, + &cb_in_1.nb); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &(cb_in_1.cb_completion), HZ / 2), + >, 0); + UT_ASSERT_INT(cb_in_1.cb_count, ==, 1); + UT_ASSERT_INT(cb_in_1.event_entry_update, ==, 0); + + ret = msm_smp2p_in_register(SMP2P_REMOTE_MOCK_PROC, + rmp->remote_item.entries[0].name, + &cb_in_2.nb); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &(cb_in_2.cb_completion), HZ / 2), + >, 0); + UT_ASSERT_INT(cb_in_2.cb_count, ==, 1); + UT_ASSERT_INT(cb_in_2.event_entry_update, ==, 0); + + + /* Unregister the clients */ + ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC, + rmp->remote_item.entries[0].name, + &(cb_in_1.nb)); + UT_ASSERT_INT(ret, ==, 0); + + ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC, + rmp->remote_item.entries[0].name, + &(cb_in_2.nb)); + UT_ASSERT_INT(ret, ==, 0); + + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + + ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC, + rmp->remote_item.entries[0].name, + &(cb_in_1.nb)); + + ret = msm_smp2p_in_unregister(SMP2P_REMOTE_MOCK_PROC, + rmp->remote_item.entries[0].name, + &(cb_in_2.nb)); + } +} + +/** + * smp2p_ut_local_ssr_ack - Verify SSR Done/ACK Feature + * + * @s: pointer to output file + */ +static void smp2p_ut_local_ssr_ack(struct seq_file *s) +{ + int failed = 0; + struct msm_smp2p_remote_mock *rmp = NULL; + int ret; + + seq_printf(s, "Running %s\n", __func__); + do { + struct smp2p_smem *rhdr; + struct smp2p_smem *lhdr; + int negotiation_state; + + /* initialize v1 without SMP2P_FEATURE_SSR_ACK enabled */ + ret = smp2p_reset_mock_edge(); + UT_ASSERT_INT(ret, ==, 0); + rmp = msm_smp2p_get_remote_mock(); + UT_ASSERT_PTR(rmp, !=, NULL); + rhdr = &rmp->remote_item.header; + + rmp->rx_interrupt_count = 0; + memset(&rmp->remote_item, 0, sizeof(struct smp2p_smem_item)); + rhdr->magic = SMP2P_MAGIC; + SMP2P_SET_LOCAL_PID(rhdr->rem_loc_proc_id, + SMP2P_REMOTE_MOCK_PROC); + SMP2P_SET_REMOTE_PID(rhdr->rem_loc_proc_id, SMP2P_APPS_PROC); + SMP2P_SET_VERSION(rhdr->feature_version, 1); + SMP2P_SET_FEATURES(rhdr->feature_version, 0); + SMP2P_SET_ENT_TOTAL(rhdr->valid_total_ent, SMP2P_MAX_ENTRY); + SMP2P_SET_ENT_VALID(rhdr->valid_total_ent, 0); + rhdr->flags = 0x0; + msm_smp2p_set_remote_mock_exists(true); + rmp->tx_interrupt(); + + /* verify edge is open */ + lhdr = smp2p_get_out_item(SMP2P_REMOTE_MOCK_PROC, + &negotiation_state); + UT_ASSERT_PTR(NULL, !=, lhdr); + UT_ASSERT_INT(negotiation_state, ==, SMP2P_EDGE_STATE_OPENED); + UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1); + + /* verify no response to ack feature */ + rmp->rx_interrupt_count = 0; + SMP2P_SET_RESTART_DONE(rhdr->flags, 1); + rmp->tx_interrupt(); + UT_ASSERT_INT(0, ==, SMP2P_GET_RESTART_DONE(lhdr->flags)); + UT_ASSERT_INT(0, ==, SMP2P_GET_RESTART_ACK(lhdr->flags)); + UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 0); + + /* initialize v1 with SMP2P_FEATURE_SSR_ACK enabled */ + ret = smp2p_reset_mock_edge(); + UT_ASSERT_INT(ret, ==, 0); + rmp = msm_smp2p_get_remote_mock(); + UT_ASSERT_PTR(rmp, !=, NULL); + rhdr = &rmp->remote_item.header; + + rmp->rx_interrupt_count = 0; + memset(&rmp->remote_item, 0, sizeof(struct smp2p_smem_item)); + rhdr->magic = SMP2P_MAGIC; + SMP2P_SET_LOCAL_PID(rhdr->rem_loc_proc_id, + SMP2P_REMOTE_MOCK_PROC); + SMP2P_SET_REMOTE_PID(rhdr->rem_loc_proc_id, SMP2P_APPS_PROC); + SMP2P_SET_VERSION(rhdr->feature_version, 1); + SMP2P_SET_FEATURES(rhdr->feature_version, + SMP2P_FEATURE_SSR_ACK); + SMP2P_SET_ENT_TOTAL(rhdr->valid_total_ent, SMP2P_MAX_ENTRY); + SMP2P_SET_ENT_VALID(rhdr->valid_total_ent, 0); + rmp->rx_interrupt_count = 0; + rhdr->flags = 0x0; + msm_smp2p_set_remote_mock_exists(true); + rmp->tx_interrupt(); + + /* verify edge is open */ + lhdr = smp2p_get_out_item(SMP2P_REMOTE_MOCK_PROC, + &negotiation_state); + UT_ASSERT_PTR(NULL, !=, lhdr); + UT_ASSERT_INT(negotiation_state, ==, SMP2P_EDGE_STATE_OPENED); + UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1); + + /* verify response to ack feature */ + rmp->rx_interrupt_count = 0; + SMP2P_SET_RESTART_DONE(rhdr->flags, 1); + rmp->tx_interrupt(); + UT_ASSERT_INT(0, ==, SMP2P_GET_RESTART_DONE(lhdr->flags)); + UT_ASSERT_INT(1, ==, SMP2P_GET_RESTART_ACK(lhdr->flags)); + UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1); + + rmp->rx_interrupt_count = 0; + SMP2P_SET_RESTART_DONE(rhdr->flags, 0); + rmp->tx_interrupt(); + UT_ASSERT_INT(0, ==, SMP2P_GET_RESTART_DONE(lhdr->flags)); + UT_ASSERT_INT(0, ==, SMP2P_GET_RESTART_ACK(lhdr->flags)); + UT_ASSERT_INT(rmp->rx_interrupt_count, ==, 1); + + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + } +} + +/** + * get_ssr_name_for_proc - Retrieve an SSR name from the provided list + * + * @names: List of possible processor names + * @name_len: The length of @names + * @index: Index into @names + * + * Return: Pointer to the next processor name, NULL in error conditions + */ +static char *get_ssr_name_for_proc(char *names[], size_t name_len, int index) +{ + if (index >= name_len) { + pr_err("%s: SSR failed; check subsys name table\n", + __func__); + return NULL; + } + + return names[index]; +} + +/** + * smp2p_ut_local_ssr_ack - Verify SSR Done/ACK Feature + * + * @s: pointer to output file + * @rpid: Remote processor ID + * @int_cfg: Interrupt config + */ +static void smp2p_ut_remotesubsys_ssr_ack(struct seq_file *s, uint32_t rpid, + struct smp2p_interrupt_config *int_cfg) +{ + int failed = 0; + + seq_printf(s, "Running %s\n", __func__); + do { + struct smp2p_smem *rhdr; + struct smp2p_smem *lhdr; + int negotiation_state; + int name_index; + int ret; + uint32_t ssr_done_start; + bool ssr_ack_enabled = false; + bool ssr_success = false; + char *name = NULL; + + static char *mpss_names[] = {"modem", "mpss"}; + static char *lpass_names[] = {"adsp", "lpass"}; + static char *sensor_names[] = {"slpi", "dsps"}; + static char *wcnss_names[] = {"wcnss"}; + + lhdr = smp2p_get_out_item(rpid, &negotiation_state); + UT_ASSERT_PTR(NULL, !=, lhdr); + UT_ASSERT_INT(SMP2P_EDGE_STATE_OPENED, ==, negotiation_state); + + rhdr = smp2p_get_in_item(rpid); + UT_ASSERT_PTR(NULL, !=, rhdr); + + /* get initial state of SSR flags */ + if (SMP2P_GET_FEATURES(rhdr->feature_version) + & SMP2P_FEATURE_SSR_ACK) + ssr_ack_enabled = true; + else + ssr_ack_enabled = false; + + ssr_done_start = SMP2P_GET_RESTART_DONE(rhdr->flags); + UT_ASSERT_INT(ssr_done_start, ==, + SMP2P_GET_RESTART_ACK(lhdr->flags)); + + /* trigger restart */ + name_index = 0; + while (!ssr_success) { + + switch (rpid) { + case SMP2P_MODEM_PROC: + name = get_ssr_name_for_proc(mpss_names, + ARRAY_SIZE(mpss_names), + name_index); + break; + case SMP2P_AUDIO_PROC: + name = get_ssr_name_for_proc(lpass_names, + ARRAY_SIZE(lpass_names), + name_index); + break; + case SMP2P_SENSOR_PROC: + name = get_ssr_name_for_proc(sensor_names, + ARRAY_SIZE(sensor_names), + name_index); + break; + case SMP2P_WIRELESS_PROC: + name = get_ssr_name_for_proc(wcnss_names, + ARRAY_SIZE(wcnss_names), + name_index); + break; + default: + pr_err("%s: Invalid proc ID %d given for ssr\n", + __func__, rpid); + } + + if (!name) { + seq_puts(s, "\tSSR failed; check subsys name table\n"); + failed = true; + break; + } + + seq_printf(s, "Restarting '%s'\n", name); + ret = subsystem_restart(name); + if (ret == -ENODEV) { + seq_puts(s, "\tSSR call failed\n"); + ++name_index; + continue; + } + ssr_success = true; + } + if (failed) + break; + + msleep(10*1000); + + /* verify ack signaling */ + if (ssr_ack_enabled) { + ssr_done_start ^= 1; + UT_ASSERT_INT(ssr_done_start, ==, + SMP2P_GET_RESTART_ACK(lhdr->flags)); + UT_ASSERT_INT(ssr_done_start, ==, + SMP2P_GET_RESTART_DONE(rhdr->flags)); + UT_ASSERT_INT(0, ==, + SMP2P_GET_RESTART_DONE(lhdr->flags)); + seq_puts(s, "\tSSR ACK Enabled and Toggled\n"); + } else { + UT_ASSERT_INT(0, ==, + SMP2P_GET_RESTART_DONE(lhdr->flags)); + UT_ASSERT_INT(0, ==, + SMP2P_GET_RESTART_ACK(lhdr->flags)); + + UT_ASSERT_INT(0, ==, + SMP2P_GET_RESTART_DONE(rhdr->flags)); + UT_ASSERT_INT(0, ==, + SMP2P_GET_RESTART_ACK(rhdr->flags)); + seq_puts(s, "\tSSR ACK Disabled\n"); + } + + seq_puts(s, "\tOK\n"); + } while (0); + + if (failed) { + pr_err("%s: Failed\n", __func__); + seq_puts(s, "\tFailed\n"); + } +} + +/** + * smp2p_ut_remote_ssr_ack - Verify SSR Done/ACK Feature + * + * @s: pointer to output file + * + * Triggers SSR for each subsystem. + */ +static void smp2p_ut_remote_ssr_ack(struct seq_file *s) +{ + struct smp2p_interrupt_config *int_cfg; + int pid; + + int_cfg = smp2p_get_interrupt_config(); + if (!int_cfg) { + seq_puts(s, + "Remote processor config unavailable\n"); + return; + } + + for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) { + if (!int_cfg[pid].is_configured) + continue; + + msm_smp2p_deinit_rmt_lpb_proc(pid); + smp2p_ut_remotesubsys_ssr_ack(s, pid, &int_cfg[pid]); + msm_smp2p_init_rmt_lpb_proc(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 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); +} + +void smp2p_debug_create_u32(const char *name, uint32_t *value) +{ + struct dentry *file; + + file = debugfs_create_u32(name, S_IRUGO | S_IWUSR, dent, value); + 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); + smp2p_debug_create("ut_local_in_multiple", + smp2p_ut_local_in_multiple); + smp2p_debug_create("ut_local_ssr_ack", + smp2p_ut_local_ssr_ack); + smp2p_debug_create("ut_remote_ssr_ack", + smp2p_ut_remote_ssr_ack); + + return 0; +} +module_init(smp2p_debugfs_init); diff --git a/drivers/soc/qcom/smp2p_test_common.h b/drivers/soc/qcom/smp2p_test_common.h new file mode 100644 index 000000000000..747a812d82c5 --- /dev/null +++ b/drivers/soc/qcom/smp2p_test_common.h @@ -0,0 +1,213 @@ +/* drivers/soc/qcom/smp2p_test_common.h + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _SMP2P_TEST_COMMON_H_ +#define _SMP2P_TEST_COMMON_H_ + +#include <linux/debugfs.h> + +/** + * Unit test assertion for logging test cases. + * + * @a lval + * @b rval + * @cmp comparison operator + * + * Assertion fails if (@a cmp @b) is not true which then + * logs the function and line number where the error occurred + * along with the values of @a and @b. + * + * Assumes that the following local variables exist: + * @s - sequential output file pointer + * @failed - set to true if test fails + */ +#define UT_ASSERT_INT(a, cmp, b) \ + { \ + int a_tmp = (a); \ + int b_tmp = (b); \ + if (!((a_tmp)cmp(b_tmp))) { \ + seq_printf(s, "%s:%d Fail: " #a "(%d) " #cmp " " #b "(%d)\n", \ + __func__, __LINE__, \ + a_tmp, b_tmp); \ + failed = 1; \ + break; \ + } \ + } + +#define UT_ASSERT_PTR(a, cmp, b) \ + { \ + void *a_tmp = (a); \ + void *b_tmp = (b); \ + if (!((a_tmp)cmp(b_tmp))) { \ + seq_printf(s, "%s:%d Fail: " #a "(%p) " #cmp " " #b "(%p)\n", \ + __func__, __LINE__, \ + a_tmp, b_tmp); \ + failed = 1; \ + break; \ + } \ + } + +#define UT_ASSERT_UINT(a, cmp, b) \ + { \ + unsigned a_tmp = (a); \ + unsigned b_tmp = (b); \ + if (!((a_tmp)cmp(b_tmp))) { \ + seq_printf(s, "%s:%d Fail: " #a "(%u) " #cmp " " #b "(%u)\n", \ + __func__, __LINE__, \ + a_tmp, b_tmp); \ + failed = 1; \ + break; \ + } \ + } + +#define UT_ASSERT_HEX(a, cmp, b) \ + { \ + unsigned a_tmp = (a); \ + unsigned b_tmp = (b); \ + if (!((a_tmp)cmp(b_tmp))) { \ + seq_printf(s, "%s:%d Fail: " #a "(%x) " #cmp " " #b "(%x)\n", \ + __func__, __LINE__, \ + a_tmp, b_tmp); \ + failed = 1; \ + break; \ + } \ + } + +/** + * In-range unit test assertion for test cases. + * + * @a lval + * @minv Minimum value + * @maxv Maximum value + * + * Assertion fails if @a is not on the exclusive range minv, maxv + * ((@a < @minv) or (@a > @maxv)). In the failure case, the macro + * logs the function and line number where the error occurred along + * with the values of @a and @minv, @maxv. + * + * Assumes that the following local variables exist: + * @s - sequential output file pointer + * @failed - set to true if test fails + */ +#define UT_ASSERT_INT_IN_RANGE(a, minv, maxv) \ + { \ + int a_tmp = (a); \ + int minv_tmp = (minv); \ + int maxv_tmp = (maxv); \ + if (((a_tmp) < (minv_tmp)) || ((a_tmp) > (maxv_tmp))) { \ + seq_printf(s, "%s:%d Fail: " #a "(%d) < " #minv "(%d) or " \ + #a "(%d) > " #maxv "(%d)\n", \ + __func__, __LINE__, \ + a_tmp, minv_tmp, a_tmp, maxv_tmp); \ + failed = 1; \ + break; \ + } \ + } + +/* Structure to track state changes for the notifier callback. */ +struct mock_cb_data { + bool initialized; + spinlock_t lock; + struct notifier_block nb; + + /* events */ + struct completion cb_completion; + int cb_count; + int event_open; + int event_entry_update; + struct msm_smp2p_update_notif entry_data; +}; + +void smp2p_debug_create(const char *name, void (*show)(struct seq_file *)); +void smp2p_debug_create_u32(const char *name, uint32_t *value); +static inline int smp2p_test_notify(struct notifier_block *self, + unsigned long event, void *data); + +/** + * Reset mock callback data to default values. + * + * @cb: Mock callback data + */ +static inline void mock_cb_data_reset(struct mock_cb_data *cb) +{ + reinit_completion(&cb->cb_completion); + cb->cb_count = 0; + cb->event_open = 0; + cb->event_entry_update = 0; + memset(&cb->entry_data, 0, + sizeof(struct msm_smp2p_update_notif)); +} + + +/** + * Initialize mock callback data. + * + * @cb: Mock callback data + */ +static inline void mock_cb_data_init(struct mock_cb_data *cb) +{ + if (!cb->initialized) { + init_completion(&cb->cb_completion); + spin_lock_init(&cb->lock); + cb->initialized = true; + cb->nb.notifier_call = smp2p_test_notify; + memset(&cb->entry_data, 0, + sizeof(struct msm_smp2p_update_notif)); + } + mock_cb_data_reset(cb); +} + +/** + * Notifier function passed into SMP2P for testing. + * + * @self: Pointer to calling notifier block + * @event: Event + * @data: Event-specific data + * @returns: 0 + */ +static inline int smp2p_test_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + struct mock_cb_data *cb_data_ptr; + unsigned long flags; + + cb_data_ptr = container_of(self, struct mock_cb_data, nb); + + spin_lock_irqsave(&cb_data_ptr->lock, flags); + + switch (event) { + case SMP2P_OPEN: + ++cb_data_ptr->event_open; + if (data) { + cb_data_ptr->entry_data = + *(struct msm_smp2p_update_notif *)(data); + } + break; + case SMP2P_ENTRY_UPDATE: + ++cb_data_ptr->event_entry_update; + if (data) { + cb_data_ptr->entry_data = + *(struct msm_smp2p_update_notif *)(data); + } + break; + default: + pr_err("%s Unknown event\n", __func__); + break; + } + + ++cb_data_ptr->cb_count; + complete(&cb_data_ptr->cb_completion); + spin_unlock_irqrestore(&cb_data_ptr->lock, flags); + return 0; +} +#endif /* _SMP2P_TEST_COMMON_H_ */ |