1725 lines
49 KiB
C
1725 lines
49 KiB
C
/* arch/arm/mach-msm/smp2p.c
|
|
*
|
|
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
#include <linux/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 <mach/msm_smem.h>
|
|
#include <mach/msm_ipc_logging.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.
|
|
*/
|
|
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;
|
|
};
|
|
static struct smp2p_out_list_item out_list[SMP2P_NUM_PROCS];
|
|
|
|
static void *log_ctx;
|
|
static int smp2p_debug_mask = MSM_SMP2P_INFO;
|
|
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
|
|
* @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 (*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);
|
|
|
|
/* 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_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);
|
|
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_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);
|
|
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,
|
|
.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,
|
|
.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_WIRELESS_PROC].name = "wcnss",
|
|
[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_WIRELESS_PROC:
|
|
ret = SMEM_SMP2P_WIRLESS_BASE + read_pid;
|
|
break;
|
|
case SMP2P_POWER_PROC:
|
|
ret = SMEM_SMP2P_POWER_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);
|
|
|
|
if (!item_ptr) {
|
|
size = sizeof(struct smp2p_smem_item);
|
|
item_ptr = smem_alloc2(smem_id, size);
|
|
}
|
|
}
|
|
} 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;
|
|
|
|
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);
|
|
} 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_negotiate_features_v0 - 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)
|
|
{
|
|
/* no supported features */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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) {
|
|
if (pos->name[0]) {
|
|
if (!strncmp(pos->name, name, SMP2P_MAX_ENTRY_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));
|
|
strlcpy(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.
|
|
* @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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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_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.
|
|
* @returns: -ENODEV
|
|
*/
|
|
static int smp2p_out_modify_v0(struct msm_smp2p_out *out_entry,
|
|
uint32_t set_mask, uint32_t clear_mask)
|
|
{
|
|
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: %d 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->reserved = 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, 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->smem_edge_state = SMP2P_EDGE_STATE_OPENED;
|
|
out_item->ops_ptr = &version_if[l_version];
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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.
|
|
* @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)
|
|
{
|
|
int ret = -EINVAL;
|
|
unsigned long flags;
|
|
struct smp2p_out_list_item *out_item;
|
|
|
|
if (!handle)
|
|
return ret;
|
|
|
|
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);
|
|
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;
|
|
|
|
if (remote_pid >= SMP2P_NUM_PROCS)
|
|
return -EINVAL;
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
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)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) {
|
|
spin_unlock_irqrestore(&out_list[remote_pid].out_item_lock_lha1,
|
|
flags);
|
|
smp2p_in_edge_notify(remote_pid);
|
|
} 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;
|
|
|
|
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 *)remote_pid);
|
|
}
|
|
|
|
/**
|
|
* msm_smp2p_probe - Device tree probe function.
|
|
*
|
|
* @pdev: Pointer to device tree data.
|
|
* @returns: 0 on success; -ENODEV otherwise
|
|
*/
|
|
static int __devinit msm_smp2p_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *r;
|
|
void *irq_out_ptr;
|
|
char *key;
|
|
uint32_t edge;
|
|
int ret;
|
|
struct device_node *node;
|
|
uint32_t irq_bitmask;
|
|
uint32_t irq_line;
|
|
|
|
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);
|
|
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);
|
|
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);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
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;
|
|
|
|
ret = request_irq(irq_line, smp2p_interrupt_handler,
|
|
IRQF_TRIGGER_RISING, "smp2p", (void *)edge);
|
|
if (ret < 0) {
|
|
SMP2P_ERR("%s: request_irq() failed on %d (edge %d)\n",
|
|
__func__, irq_line, edge);
|
|
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);
|
|
fail:
|
|
return -ENODEV;
|
|
}
|
|
|
|
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];
|
|
|
|
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");
|
|
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");
|