soc: qcom: Add snapshot of G-Link driver

This is a snapshot of the G-Link driver taken as of msm-4.4
commit <aaf356abef2> (Merge "scsi: ufs: add 2 lane support").

In addition, fix coding style issues, refactor usage
of waitqueue_active and remove BUG_ON() calls.

CRs-Fixed: 1059650
Change-Id: Ia1f888b19fa6102ac3fa0c9d0c8bb2447d3d4346
Signed-off-by: Chris Lew <clew@codeaurora.org>
This commit is contained in:
Chris Lew 2016-08-01 13:29:46 -07:00
commit fa6135ecbe
24 changed files with 18441 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
Qualcomm Technologies, Inc. G-link QoS Configuration
Required properties:
-compatible : should be "qcom,glink-qos-config"
-qcom,flow-info : A table of MTU transmission time specific for a power state.
The total number of entries in the table should be equal to the
number of supported flows.
-qcom,mtu-size : The MTU size for which the qos elements are configured.
-qcom,tput-stats-cycle: The number of allowable cycles for a packet to be
transmitted without its priority being re-evaluated.
Example:
qcom,glink-qos-config-adsp {
compatible = "qcom,glink-qos-config";
qcom,flow-info = <0x80 0x0>,
<0x70 0x1>,
<0x60 0x2>,
<0x50 0x3>;
qcom,mtu-size = <0x800>;
qcom,tput-stats-cycle = <0xa>;
};

View file

@ -0,0 +1,35 @@
Qualcomm Technologies, Inc. G-link Mailbox Transport
Required properties:
-compatible : should be "qcom,glink-mailbox-xprt"
-reg : the mailbox register to store the location of the fifo
the mailbox register to store the size of the fifo
the irq register base address for triggering interrupts
the register to enable sending interrupts
the register to reset the rx irq line
-reg-names : "mbox-loc-addr" - string to identify the mailbox location reg
"mbox-loc-size" - string to identify the mailbox size reg
"irq-reg-base" - string to identify the irq register region
"irq-rx-reset" - string to identify the rx irq reset register
-qcom,irq-mask : the bitmask to trigger an interrupt
-interrupts : the receiving interrupt line
-label : the name of the subsystem this link connects to
-qcom,tx-ring-size: size of the transmit ring buffer in bytes
-qcom,rx-ring-size: size of the receive ring buffer in bytes
Example:
qcom,glink-mailbox-xprt-spss@1d05008 {
compatible = "qcom,glink-mailbox-xprt";
reg = <0x1d05008 0x8>,
<0x1d05010 0x4>,
<0x1d06004 0x4>,
<0x1d06020 0x4>;
reg-names = "mbox-loc-addr", "mbox-loc-size", "irq-reg-base",
"irq-rx-reset";
qcom,irq-mask = <0x1000>;
interrupts = <0 25 4>;
label = "spss";
qcom,tx-ring-size = <0x400>;
qcom,rx-ring-size = <0x400>;
};

View file

@ -0,0 +1,21 @@
Qualcomm Technologies, Inc. G-link RPM Native Transport
Required properties:
-compatible : should be "qcom,glink-rpm-native-xprt"
-reg : the location and size of RPM message RAM
the irq register base address for triggering interrupts
-reg-names : "msgram" - string to identify the RPM message RAM region
"irq-reg-base" - string to identify the irq register region
-qcom,irq-mask : the bitmark to trigger an interrupt
-interrupts : the receiving interrupt line
Example:
qcom,glink-rpm-native-xprt@68000 {
compatible = "qcom,glink-rpm-native-xprt";
reg = <0x68000 0x8000>,
<0xfa006008 0x4>;
reg-names = "msgram", "irq-reg-base";
qcom,irq-mask = <0x1000>;
interrupts = <0 25 1>;
};

View file

@ -0,0 +1,54 @@
Qualcomm Technologies, Inc. G-link SMEM Native Transport
Required properties:
-compatible : should be "qcom,glink-smem-native-xprt"
-reg : the location and size of shared memory
the irq register base address for triggering interrupts
-reg-names : "smem" - string to identify the shared memory region
"irq-reg-base" - string to identify the irq register region
-qcom,irq-mask : the bitmark to trigger an interrupt
-interrupts : the receiving interrupt line
-label : the name of the subsystem this link connects to
Optional properties:
-qcom,qos-config: Reference to the qos configuration elements.It depends on
ramp-time.
-qcom,ramp-time: Worst case time in microseconds to transition to this power
state. Power states are numbered by array index position.
Example:
qcom,glink-smem-native-xprt-modem@fa00000 {
compatible = "qcom,glink-smem-native-xprt";
reg = <0xfa00000 0x200000>,
<0xfa006008 0x4>;
reg-names = "smem", "irq-reg-base";
qcom,irq-mask = <0x1000>;
interrupts = <0 25 1>;
label = "mpss";
};
qcom,glink-smem-native-xprt-adsp@fa00000 {
compatible = "qcom,glink-smem-native-xprt";
reg = <0xfa00000 0x200000>,
<0xfa006008 0x4>;
reg-names = "smem", "irq-reg-base";
qcom,irq-mask = <0x1000>;
interrupts = <0 25 1>;
label = "lpass";
qcom,qos-config = <&glink_qos_adsp>;
qcom,ramp-time = <0x10>,
<0x20>,
<0x30>,
<0x40>;
};
glink_qos_adsp: qcom,glink-qos-config-adsp {
compatible = "qcom,glink-qos-config";
qcom,flow-info = <0x80 0x0>,
<0x70 0x1>,
<0x60 0x2>,
<0x50 0x3>;
qcom,mtu-size = <0x800>;
qcom,tput-stats-cycle = <0xa>;
};

View file

@ -0,0 +1,44 @@
Qualcomm Technologies, Inc. G-link SPI Transport
Required properties:
-compatible : should be "qcom,glink-spi-xprt".
-label : the name of the subsystem this link connects to.
Optional properties:
-qcom,remote-fifo-config: Reference to the FIFO configuratio in the remote
processor.
-qcom,qos-config: Reference to the qos configuration elements.It depends on
ramp-time.
-qcom,ramp-time: Worst case time in microseconds to transition to this power
state. Power states are numbered by array index position.
Example:
glink_spi_xprt_wdsp: qcom,glink-spi-xprt-wdsp {
compatible = "qcom,glink-spi-xprt";
label = "wdsp";
qcom,remote-fifo-config = <&glink_fifo_wdsp>;
qcom,qos-config = <&glink_qos_wdsp>;
qcom,ramp-time = <0x10>,
<0x20>,
<0x30>,
<0x40>;
};
glink_fifo_wdsp: qcom,glink-fifo-config-wdsp {
compatible = "qcom,glink-fifo-config";
qcom,out-read-idx-reg = <0x12000>;
qcom,out-write-idx-reg = <0x12004>;
qcom,in-read-idx-reg = <0x1200C>;
qcom,in-write-idx-reg = <0x12010>;
};
glink_qos_wdsp: qcom,glink-qos-config-wdsp {
compatible = "qcom,glink-qos-config";
qcom,flow-info = <0x80 0x0>,
<0x70 0x1>,
<0x60 0x2>,
<0x50 0x3>;
qcom,mtu-size = <0x800>;
qcom,tput-stats-cycle = <0xa>;
};

View file

@ -0,0 +1,36 @@
Qualcomm Technologies, Inc. G-Link SSR
[Root level node]
Required properties:
-compatible : should be "qcom,glink_ssr"
-label : The name of this subsystem.
-qcom,edge : The name of the edge to this subsystem.
-qcom,notify-edges : Reference to other subsystems to notify when this
subsystem goes down.
Optional properties:
-qcom,xprt : The name of the transport on which to notify this subystem.
Example:
glink_mpss: qcom,glink-ssr-modem {
compatible = "qcom,glink_ssr";
label = "modem";
qcom,edge = "mpss";
qcom,notify-edges = <&glink_adsp>, <&glink_wcnss>;
qcom,xprt = "smem";
};
glink_adsp: qcom,glink-ssr-adsp {
compatible = "qcom,glink_ssr";
label = "adsp";
qcom,edge = "adsp";
qcom,notify-edges = <&glink_mpss>, <&glink_wcnss>;
};
glink_wcnss: qcom,glink-ssr-wcnss {
compatible = "qcom,glink_ssr";
label = "wcnss";
qcom,edge = "wcnss";
qcom,notify-edges = <&glink_mpss>;
};

View file

@ -209,3 +209,48 @@ config MSM_SMEM
Support for the shared memory interface between the various
processors in the System on a Chip (SoC) which allows basic
inter-processor communication.
config MSM_GLINK
bool "Generic Link (G-Link)"
help
G-Link is a generic link transport that replaces SMD. It is used
within a System-on-Chip (SoC) for communication between both internal
processors and external peripherals. The actual physical transport
is handled by transport plug-ins that can be individually enabled and
configured separately.
config MSM_GLINK_LOOPBACK_SERVER
bool "Generic Link (G-Link) Loopback Server"
help
G-Link Loopback Server that enable loopback test framework to test
and validate the G-Link protocol stack. It support both local and
remote clients to configure the loopback server and echo back the
data received from the clients.
config MSM_GLINK_SMEM_NATIVE_XPRT
depends on MSM_SMEM
depends on MSM_GLINK
bool "Generic Link (G-Link) SMEM Native Transport"
help
G-Link SMEM Native Transport is a G-Link Transport plug-in. It allows
G-Link communication to remote entities through a shared memory
physical transport. The nature of shared memory limits this G-Link
transport to only connecting with entities internal to the
System-on-Chip.
config MSM_GLINK_SPI_XPRT
depends on MSM_GLINK
tristate "Generic Link (G-Link) SPI Transport"
help
G-Link SPI Transport is a Transport plug-in developed over SPI
bus. This transport plug-in performs marshaling of G-Link
commands & data to the appropriate SPI bus wire format and
allows for G-Link communication with remote subsystems that are
external to the System-on-Chip.
config TRACER_PKT
bool "Tracer Packet"
help
Tracer Packet helps in profiling the performance of inter-
processor communication protocols. The profiling information
can be logged into the tracer packet itself.

View file

@ -23,3 +23,8 @@ obj-$(CONFIG_QCOM_MEMORY_DUMP_V2) += memory_dump_v2.o
obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o
obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o
obj-$(CONFIG_MSM_SMEM) += msm_smem.o smem_debug.o
obj-$(CONFIG_MSM_GLINK) += glink.o glink_debugfs.o glink_ssr.o
obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o
obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT) += glink_smem_native_xprt.o
obj-$(CONFIG_MSM_GLINK_SPI_XPRT) += glink_spi_xprt.o
obj-$(CONFIG_TRACER_PKT) += tracer_pkt.o

6036
drivers/soc/qcom/glink.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,218 @@
/* Copyright (c) 2014-2016, 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 _SOC_QCOM_GLINK_CORE_IF_H_
#define _SOC_QCOM_GLINK_CORE_IF_H_
#include <linux/of.h>
#include <linux/types.h>
#include "glink_private.h"
/* Local Channel state */
enum local_channel_state_e {
GLINK_CHANNEL_CLOSED = 0,
GLINK_CHANNEL_OPENING,
GLINK_CHANNEL_OPENED,
GLINK_CHANNEL_CLOSING,
};
/* Transport Negotiation State */
enum transport_state_e {
GLINK_XPRT_DOWN,
GLINK_XPRT_NEGOTIATING,
GLINK_XPRT_OPENED,
GLINK_XPRT_FAILED,
};
struct channel_ctx;
struct glink_core_xprt_ctx;
struct glink_transport_if;
struct glink_core_version;
/**
* struct glink_core_version - Individual version element
*
* version: supported version
* features: all supported features for version
*/
struct glink_core_version {
uint32_t version;
uint32_t features;
uint32_t (*negotiate_features)(struct glink_transport_if *if_ptr,
const struct glink_core_version *version_ptr,
uint32_t features);
};
/**
* RX intent
*
* data: pointer to the data (may be NULL for zero-copy)
* id: remote or local intent ID
* pkt_size: total size of packet
* write_offset: next write offset (initially 0)
* intent_size: size of the original intent (do not modify)
* tracer_pkt: Flag to indicate if the data is a tracer packet
* iovec: Pointer to vector buffer if the transport passes a vector buffer
* vprovider: Virtual address-space buffer provider for a vector buffer
* pprovider: Physical address-space buffer provider for a vector buffer
* cookie: Private transport specific cookie
* pkt_priv: G-Link core owned packet-private data
* list: G-Link core owned list node
* bounce_buf: Pointer to the temporary/internal bounce buffer
*/
struct glink_core_rx_intent {
void *data;
uint32_t id;
size_t pkt_size;
size_t write_offset;
size_t intent_size;
bool tracer_pkt;
void *iovec;
void * (*vprovider)(void *iovec, size_t offset, size_t *size);
void * (*pprovider)(void *iovec, size_t offset, size_t *size);
void *cookie;
/* G-Link-Core-owned elements - please ignore */
struct list_head list;
const void *pkt_priv;
void *bounce_buf;
};
/**
* struct glink_core_flow_info - Flow specific Information
* @mtu_tx_time_us: Time to transmit an MTU in microseconds.
* @power_state: Power state associated with the traffic flow.
*/
struct glink_core_flow_info {
unsigned long mtu_tx_time_us;
uint32_t power_state;
};
/**
* struct glink_core_transport_cfg - configuration of a new transport
* @name: Name of the transport.
* @edge: Subsystem the transport connects to.
* @versions: Array of transport versions supported.
* @versions_entries: Number of entries in @versions.
* @max_cid: Maximum number of channel identifiers supported.
* @max_iid: Maximum number of intent identifiers supported.
* @mtu: MTU supported by this transport.
* @num_flows: Number of traffic flows/priority buckets.
* @flow_info: Information about each flow/priority.
* @token_count: Number of tokens per assignment.
*/
struct glink_core_transport_cfg {
const char *name;
const char *edge;
const struct glink_core_version *versions;
size_t versions_entries;
uint32_t max_cid;
uint32_t max_iid;
size_t mtu;
uint32_t num_flows;
struct glink_core_flow_info *flow_info;
uint32_t token_count;
};
struct glink_core_if {
/* Negotiation */
void (*link_up)(struct glink_transport_if *if_ptr);
void (*link_down)(struct glink_transport_if *if_ptr);
void (*rx_cmd_version)(struct glink_transport_if *if_ptr,
uint32_t version,
uint32_t features);
void (*rx_cmd_version_ack)(struct glink_transport_if *if_ptr,
uint32_t version,
uint32_t features);
/* channel management */
void (*rx_cmd_ch_remote_open)(struct glink_transport_if *if_ptr,
uint32_t rcid, const char *name, uint16_t req_xprt);
void (*rx_cmd_ch_open_ack)(struct glink_transport_if *if_ptr,
uint32_t lcid, uint16_t xprt_resp);
void (*rx_cmd_ch_remote_close)(struct glink_transport_if *if_ptr,
uint32_t rcid);
void (*rx_cmd_ch_close_ack)(struct glink_transport_if *if_ptr,
uint32_t lcid);
/* channel data */
struct glink_core_rx_intent * (*rx_get_pkt_ctx)(
struct glink_transport_if *if_ptr,
uint32_t rcid, uint32_t liid);
void (*rx_put_pkt_ctx)(struct glink_transport_if *if_ptr, uint32_t rcid,
struct glink_core_rx_intent *intent_ptr, bool complete);
void (*rx_cmd_remote_rx_intent_put)(struct glink_transport_if *if_ptr,
uint32_t rcid, uint32_t riid, size_t size);
void (*rx_cmd_remote_rx_intent_put_cookie)(
struct glink_transport_if *if_ptr, uint32_t rcid,
uint32_t riid, size_t size, void *cookie);
void (*rx_cmd_tx_done)(struct glink_transport_if *if_ptr, uint32_t rcid,
uint32_t riid, bool reuse);
void (*rx_cmd_remote_rx_intent_req)(struct glink_transport_if *if_ptr,
uint32_t rcid, size_t size);
void (*rx_cmd_rx_intent_req_ack)(struct glink_transport_if *if_ptr,
uint32_t rcid, bool granted);
void (*rx_cmd_remote_sigs)(struct glink_transport_if *if_ptr,
uint32_t rcid, uint32_t sigs);
/* channel scheduling */
void (*tx_resume)(struct glink_transport_if *if_ptr);
};
int glink_core_register_transport(struct glink_transport_if *if_ptr,
struct glink_core_transport_cfg *cfg);
void glink_core_unregister_transport(struct glink_transport_if *if_ptr);
/**
* of_get_glink_core_qos_cfg() - Parse the qos related dt entries
* @phandle: The handle to the qos related node in DT.
* @cfg: The transport configuration to be filled.
*
* Return: 0 on Success, standard Linux error otherwise.
*/
int of_get_glink_core_qos_cfg(struct device_node *phandle,
struct glink_core_transport_cfg *cfg);
/**
* rx_linear_vbuf_provider() - Virtual Buffer Provider for linear buffers
* iovec: Pointer to the beginning of the linear buffer.
* offset: Offset into the buffer whose address is needed.
* size: Pointer to hold the length of the contiguous buffer space.
*
* This function is used when a linear buffer is received while the client has
* registered to receive vector buffers.
*
* Return: Address of the buffer which is at offset "offset" from the beginning
* of the buffer.
*/
static inline void *rx_linear_vbuf_provider(void *iovec, size_t offset,
size_t *size)
{
struct glink_core_rx_intent *rx_info =
(struct glink_core_rx_intent *)iovec;
if (unlikely(!iovec || !size))
return NULL;
if (unlikely(offset >= rx_info->pkt_size))
return NULL;
if (unlikely(OVERFLOW_ADD_UNSIGNED(void *, rx_info->data, offset)))
return NULL;
*size = rx_info->pkt_size - offset;
return rx_info->data + offset;
}
#endif /* _SOC_QCOM_GLINK_CORE_IF_H_ */

View file

@ -0,0 +1,788 @@
/* Copyright (c) 2014-2016, 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/err.h>
#include <linux/ipc_logging.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <soc/qcom/glink.h>
#include "glink_private.h"
#include "glink_core_if.h"
static const char * const ss_string[] = {
[GLINK_DBGFS_MPSS] = "mpss",
[GLINK_DBGFS_APSS] = "apss",
[GLINK_DBGFS_LPASS] = "lpass",
[GLINK_DBGFS_DSPS] = "dsps",
[GLINK_DBGFS_RPM] = "rpm",
[GLINK_DBGFS_WCNSS] = "wcnss",
[GLINK_DBGFS_LLOOP] = "lloop",
[GLINK_DBGFS_MOCK] = "mock"
};
static const char * const xprt_string[] = {
[GLINK_DBGFS_SMEM] = "smem",
[GLINK_DBGFS_SMD] = "smd",
[GLINK_DBGFS_XLLOOP] = "lloop",
[GLINK_DBGFS_XMOCK] = "mock",
[GLINK_DBGFS_XMOCK_LOW] = "mock_low",
[GLINK_DBGFS_XMOCK_HIGH] = "mock_high"
};
static const char * const ch_st_string[] = {
[GLINK_CHANNEL_CLOSED] = "CLOSED",
[GLINK_CHANNEL_OPENING] = "OPENING",
[GLINK_CHANNEL_OPENED] = "OPENED",
[GLINK_CHANNEL_CLOSING] = "CLOSING",
};
static const char * const xprt_st_string[] = {
[GLINK_XPRT_DOWN] = "DOWN",
[GLINK_XPRT_NEGOTIATING] = "NEGOT",
[GLINK_XPRT_OPENED] = "OPENED",
[GLINK_XPRT_FAILED] = "FAILED"
};
#if defined(CONFIG_DEBUG_FS)
#define GLINK_DBGFS_NAME_SIZE (2 * GLINK_NAME_SIZE + 1)
struct glink_dbgfs_dent {
struct list_head list_node;
char par_name[GLINK_DBGFS_NAME_SIZE];
char self_name[GLINK_DBGFS_NAME_SIZE];
struct dentry *parent;
struct dentry *self;
spinlock_t file_list_lock_lhb0;
struct list_head file_list;
};
static struct dentry *dent;
static LIST_HEAD(dent_list);
static DEFINE_MUTEX(dent_list_lock_lha0);
static int debugfs_show(struct seq_file *s, void *data)
{
struct glink_dbgfs_data *dfs_d;
dfs_d = s->private;
dfs_d->o_func(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,
};
#endif
/**
* glink_get_ss_enum_string() - get the name of the subsystem based on enum
* value
* @enum_id: enum id of a specific subsystem.
*
* Return: name of the subsystem, NULL in case of invalid input
*/
const char *glink_get_ss_enum_string(unsigned int enum_id)
{
if (enum_id >= ARRAY_SIZE(ss_string))
return NULL;
return ss_string[enum_id];
}
EXPORT_SYMBOL(glink_get_ss_enum_string);
/**
* glink_get_xprt_enum_string() - get the name of the transport based on enum
* value
* @enum_id: enum id of a specific transport.
*
* Return: name of the transport, NULL in case of invalid input
*/
const char *glink_get_xprt_enum_string(unsigned int enum_id)
{
if (enum_id >= ARRAY_SIZE(xprt_string))
return NULL;
return xprt_string[enum_id];
}
EXPORT_SYMBOL(glink_get_xprt_enum_string);
/**
* glink_get_xprt_state_string() - get the name of the transport based on enum
* value
* @enum_id: enum id of the state of the transport.
*
* Return: name of the transport state, NULL in case of invalid input
*/
const char *glink_get_xprt_state_string(
enum transport_state_e enum_id)
{
if (enum_id >= ARRAY_SIZE(xprt_st_string))
return NULL;
return xprt_st_string[enum_id];
}
EXPORT_SYMBOL(glink_get_xprt_state_string);
/**
* glink_get_ch_state_string() - get the name of the transport based on enum
* value
* @enum_id: enum id of a specific state of the channel.
*
* Return: name of the channel state, NULL in case of invalid input
*/
const char *glink_get_ch_state_string(
enum local_channel_state_e enum_id)
{
if (enum_id >= ARRAY_SIZE(ch_st_string))
return NULL;
return ch_st_string[enum_id];
}
EXPORT_SYMBOL(glink_get_ch_state_string);
#if defined(CONFIG_DEBUG_FS)
/**
* glink_dfs_create_file() - create the debugfs file
* @name: debugfs file name
* @parent: pointer to the parent dentry structure
* @show: pointer to the actual function which will be invoked upon
* opening this file.
*
* Return: pointer to the allocated glink_dbgfs_data structure or
* NULL in case of an error.
*
* This function actually create a debugfs file under the parent directory
*/
static struct glink_dbgfs_data *glink_dfs_create_file(const char *name,
struct dentry *parent, void (*show)(struct seq_file *s),
void *dbgfs_data, bool b_free_req)
{
struct dentry *file;
struct glink_dbgfs_data *dfs_d;
dfs_d = kzalloc(sizeof(struct glink_dbgfs_data), GFP_KERNEL);
if (dfs_d == NULL)
return NULL;
dfs_d->o_func = show;
if (dbgfs_data != NULL) {
dfs_d->priv_data = dbgfs_data;
dfs_d->b_priv_free_req = b_free_req;
}
file = debugfs_create_file(name, 0400, parent, dfs_d, &debug_ops);
if (!file)
GLINK_DBG("%s: unable to create file '%s'\n", __func__,
name);
dfs_d->dent = file;
return dfs_d;
}
/**
* write_ch_intent() - write channel intent details
* @s: pointer to the sequential file
* @intent: pointer glink core intent structure
* @i_type: type of intent
* @count: serial number of the intent.
*
* This function is a helper function of glink_dfs_update_ch_intents()
* that prints out details of any specific intent.
*/
static void write_ch_intent(struct seq_file *s,
struct glink_core_rx_intent *intent,
char *i_type, unsigned int count)
{
char *intent_type;
/*
* formatted, human readable channel state output, ie:
* TYPE |SN |ID |PKT_SIZE|W_OFFSET|INT_SIZE|
* --------------------------------------------------------------
* LOCAL_LIST|#2 |1 |0 |0 |8 |
*/
if (count == 1) {
intent_type = i_type;
seq_puts(s,
"\n--------------------------------------------------------\n");
} else {
intent_type = "";
}
seq_printf(s, "%-20s|#%-5d|%-6u|%-10zu|%-10zu|%-10zu|\n",
intent_type,
count,
intent->id,
intent->pkt_size,
intent->write_offset,
intent->intent_size);
}
/**
* glink_dfs_update_ch_intent() - writes the intent details of a specific
* channel to the corresponding debugfs file
* @s: pointer to the sequential file
*
* This function extracts the intent details of a channel & prints them to
* corrseponding debugfs file of that channel.
*/
static void glink_dfs_update_ch_intent(struct seq_file *s)
{
struct glink_dbgfs_data *dfs_d;
struct channel_ctx *ch_ctx;
struct glink_core_rx_intent *intent;
struct glink_core_rx_intent *intent_temp;
struct glink_ch_intent_info ch_intent_info;
unsigned long flags;
unsigned int count = 0;
dfs_d = s->private;
ch_ctx = dfs_d->priv_data;
if (ch_ctx != NULL) {
glink_get_ch_intent_info(ch_ctx, &ch_intent_info);
seq_puts(s,
"---------------------------------------------------------------\n");
seq_printf(s, "%-20s|%-6s|%-6s|%-10s|%-10s|%-10s|\n",
"INTENT TYPE",
"SN",
"ID",
"PKT_SIZE",
"W_OFFSET",
"INT_SIZE");
seq_puts(s,
"---------------------------------------------------------------\n");
spin_lock_irqsave(ch_intent_info.li_lst_lock, flags);
list_for_each_entry_safe(intent, intent_temp,
ch_intent_info.li_avail_list, list) {
count++;
write_ch_intent(s, intent, "LOCAL_AVAIL_LIST", count);
}
count = 0;
list_for_each_entry_safe(intent, intent_temp,
ch_intent_info.li_used_list, list) {
count++;
write_ch_intent(s, intent, "LOCAL_USED_LIST", count);
}
spin_unlock_irqrestore(ch_intent_info.li_lst_lock, flags);
count = 0;
spin_lock_irqsave(ch_intent_info.ri_lst_lock, flags);
list_for_each_entry_safe(intent, intent_temp,
ch_intent_info.ri_list, list) {
count++;
write_ch_intent(s, intent, "REMOTE_LIST", count);
}
spin_unlock_irqrestore(ch_intent_info.ri_lst_lock,
flags);
seq_puts(s,
"---------------------------------------------------------------\n");
}
}
/**
* glink_dfs_update_ch_stats() - writes statistics of a specific
* channel to the corresponding debugfs file
* @s: pointer to the sequential file
*
* This function extracts other statistics of a channel & prints them to
* corrseponding debugfs file of that channel
*/
static void glink_dfs_update_ch_stats(struct seq_file *s)
{
/* FUTURE: add channel statistics */
seq_puts(s, "not yet implemented\n");
}
/**
* glink_debugfs_remove_channel() - remove all channel specific files & folder
* in debugfs when channel is fully closed
* @ch_ctx: pointer to the channel_contenxt
* @xprt_ctx: pointer to the transport_context
*
* This function is invoked when any channel is fully closed. It removes the
* folders & other files in debugfs for that channel.
*/
void glink_debugfs_remove_channel(struct channel_ctx *ch_ctx,
struct glink_core_xprt_ctx *xprt_ctx){
struct glink_dbgfs ch_rm_dbgfs;
char *edge_name;
char curr_dir_name[GLINK_DBGFS_NAME_SIZE];
char *xprt_name;
ch_rm_dbgfs.curr_name = glink_get_ch_name(ch_ctx);
edge_name = glink_get_xprt_edge_name(xprt_ctx);
xprt_name = glink_get_xprt_name(xprt_ctx);
if (!xprt_name || !edge_name) {
GLINK_ERR("%s: Invalid xprt_name or edge_name for ch '%s'\n",
__func__, ch_rm_dbgfs.curr_name);
return;
}
snprintf(curr_dir_name, sizeof(curr_dir_name), "%s_%s",
edge_name, xprt_name);
ch_rm_dbgfs.par_name = curr_dir_name;
glink_debugfs_remove_recur(&ch_rm_dbgfs);
}
EXPORT_SYMBOL(glink_debugfs_remove_channel);
/**
* glink_debugfs_add_channel() - create channel specific files & folder in
* debugfs when channel is added
* @ch_ctx: pointer to the channel_contenxt
* @xprt_ctx: pointer to the transport_context
*
* This function is invoked when a new channel is created. It creates the
* folders & other files in debugfs for that channel
*/
void glink_debugfs_add_channel(struct channel_ctx *ch_ctx,
struct glink_core_xprt_ctx *xprt_ctx)
{
struct glink_dbgfs ch_dbgfs;
char *ch_name;
char *edge_name;
char *xprt_name;
char curr_dir_name[GLINK_DBGFS_NAME_SIZE];
if (ch_ctx == NULL) {
GLINK_ERR("%s: Channel Context is NULL\n", __func__);
return;
}
ch_name = glink_get_ch_name(ch_ctx);
edge_name = glink_get_xprt_edge_name(xprt_ctx);
xprt_name = glink_get_xprt_name(xprt_ctx);
if (!xprt_name || !edge_name) {
GLINK_ERR("%s: Invalid xprt_name or edge_name for ch '%s'\n",
__func__, ch_name);
return;
}
snprintf(curr_dir_name, sizeof(curr_dir_name), "%s_%s",
edge_name, xprt_name);
ch_dbgfs.curr_name = curr_dir_name;
ch_dbgfs.par_name = "channel";
ch_dbgfs.b_dir_create = true;
glink_debugfs_create(ch_name, NULL, &ch_dbgfs, NULL, false);
ch_dbgfs.par_name = ch_dbgfs.curr_name;
ch_dbgfs.curr_name = ch_name;
ch_dbgfs.b_dir_create = false;
glink_debugfs_create("stats", glink_dfs_update_ch_stats,
&ch_dbgfs, (void *)ch_ctx, false);
glink_debugfs_create("intents", glink_dfs_update_ch_intent,
&ch_dbgfs, (void *)ch_ctx, false);
}
EXPORT_SYMBOL(glink_debugfs_add_channel);
/**
* glink_debugfs_add_xprt() - create transport specific files & folder in
* debugfs when new transport is registered
* @xprt_ctx: pointer to the transport_context
*
* This function is invoked when a new transport is registered. It creates the
* folders & other files in debugfs for that transport
*/
void glink_debugfs_add_xprt(struct glink_core_xprt_ctx *xprt_ctx)
{
struct glink_dbgfs xprt_dbgfs;
char *xprt_name;
char *edge_name;
char curr_dir_name[GLINK_DBGFS_NAME_SIZE];
if (xprt_ctx == NULL)
GLINK_ERR("%s: Transport Context is NULL\n", __func__);
xprt_name = glink_get_xprt_name(xprt_ctx);
edge_name = glink_get_xprt_edge_name(xprt_ctx);
if (!xprt_name || !edge_name) {
GLINK_ERR("%s: xprt name or edge name is NULL\n", __func__);
return;
}
snprintf(curr_dir_name, sizeof(curr_dir_name), "%s_%s",
edge_name, xprt_name);
xprt_dbgfs.par_name = "glink";
xprt_dbgfs.curr_name = "xprt";
xprt_dbgfs.b_dir_create = true;
glink_debugfs_create(curr_dir_name, NULL, &xprt_dbgfs, NULL, false);
xprt_dbgfs.curr_name = "channel";
glink_debugfs_create(curr_dir_name, NULL, &xprt_dbgfs, NULL, false);
}
EXPORT_SYMBOL(glink_debugfs_add_xprt);
/**
* glink_dfs_create_channel_list() - create & update the channel details
* s: pointer to seq_file
*
* This function updates channel details in debugfs
* file present in /glink/channel/channels
*/
static void glink_dfs_create_channel_list(struct seq_file *s)
{
struct xprt_ctx_iterator xprt_iter;
struct ch_ctx_iterator ch_iter;
struct glink_core_xprt_ctx *xprt_ctx;
struct channel_ctx *ch_ctx;
int count = 0;
/*
* formatted, human readable channel state output, ie:
* NAME |LCID|RCID|XPRT|EDGE|LSTATE |RSTATE|LINT-Q|RINT-Q|
* --------------------------------------------------------------------
* LOCAL_LOOPBACK_CLNT|2 |1 |lloop|local|OPENED|OPENED|5 |6 |
* N.B. Number of TX & RX Packets not implemented yet. -ENOSYS is printed
*/
seq_printf(s, "%-20s|%-4s|%-4s|%-10s|%-6s|%-7s|%-7s|%-5s|%-5s|\n",
"NAME",
"LCID",
"RCID",
"XPRT",
"EDGE",
"LSTATE",
"RSTATE",
"LINTQ",
"RINTQ");
seq_puts(s,
"-------------------------------------------------------------------------------\n");
glink_xprt_ctx_iterator_init(&xprt_iter);
xprt_ctx = glink_xprt_ctx_iterator_next(&xprt_iter);
while (xprt_ctx != NULL) {
glink_ch_ctx_iterator_init(&ch_iter, xprt_ctx);
ch_ctx = glink_ch_ctx_iterator_next(&ch_iter);
while (ch_ctx != NULL) {
count++;
seq_printf(s, "%-20s|%-4i|%-4i|%-10s|%-6s|%-7s|",
glink_get_ch_name(ch_ctx),
glink_get_ch_lcid(ch_ctx),
glink_get_ch_rcid(ch_ctx),
glink_get_ch_xprt_name(ch_ctx),
glink_get_ch_edge_name(ch_ctx),
glink_get_ch_lstate(ch_ctx));
seq_printf(s, "%-7s|%-5i|%-5i|\n",
(glink_get_ch_rstate(ch_ctx) ? "OPENED" : "CLOSED"),
glink_get_ch_lintents_queued(ch_ctx),
glink_get_ch_rintents_queued(ch_ctx));
ch_ctx = glink_ch_ctx_iterator_next(&ch_iter);
}
glink_ch_ctx_iterator_end(&ch_iter, xprt_ctx);
xprt_ctx = glink_xprt_ctx_iterator_next(&xprt_iter);
}
glink_xprt_ctx_iterator_end(&xprt_iter);
}
/**
* glink_dfs_create_xprt_list() - create & update the transport details
* @s: pointer to seq_file
*
* This function updates channel details in debugfs file present
* in /glink/xprt/xprts
*/
static void glink_dfs_create_xprt_list(struct seq_file *s)
{
struct xprt_ctx_iterator xprt_iter;
struct glink_core_xprt_ctx *xprt_ctx;
const struct glink_core_version *gver;
uint32_t version;
uint32_t features;
int count = 0;
/*
* formatted, human readable channel state output, ie:
* XPRT_NAME|REMOTE |STATE|VERSION |FEATURES|
* ---------------------------------------------
* smd_trans|lpass |2 |0 |1 |
* smem |mpss |0 |0 |0 |
*/
seq_printf(s, "%-20s|%-20s|%-6s|%-8s|%-8s|\n",
"XPRT_NAME",
"REMOTE",
"STATE",
"VERSION",
"FEATURES");
seq_puts(s,
"-------------------------------------------------------------------------------\n");
glink_xprt_ctx_iterator_init(&xprt_iter);
xprt_ctx = glink_xprt_ctx_iterator_next(&xprt_iter);
while (xprt_ctx != NULL) {
count++;
seq_printf(s, "%-20s|%-20s|",
glink_get_xprt_name(xprt_ctx),
glink_get_xprt_edge_name(xprt_ctx));
gver = glink_get_xprt_version_features(xprt_ctx);
if (gver != NULL) {
version = gver->version;
features = gver->features;
seq_printf(s, "%-6s|%-8i|%-8i|\n",
glink_get_xprt_state(xprt_ctx),
version,
features);
} else {
seq_printf(s, "%-6s|%-8i|%-8i|\n",
glink_get_xprt_state(xprt_ctx),
-ENODATA,
-ENODATA);
}
xprt_ctx = glink_xprt_ctx_iterator_next(&xprt_iter);
}
glink_xprt_ctx_iterator_end(&xprt_iter);
}
/**
* glink_dfs_update_list() - update the internally maintained dentry linked list
* @curr_dent: pointer to the current dentry object
* @parent: pointer to the parent dentry object
* @curr: current directory name
* @par_dir: parent directory name
*/
void glink_dfs_update_list(struct dentry *curr_dent, struct dentry *parent,
const char *curr, const char *par_dir)
{
struct glink_dbgfs_dent *dbgfs_dent_s;
if (curr_dent != NULL) {
dbgfs_dent_s = kzalloc(sizeof(struct glink_dbgfs_dent),
GFP_KERNEL);
if (dbgfs_dent_s != NULL) {
INIT_LIST_HEAD(&dbgfs_dent_s->file_list);
spin_lock_init(&dbgfs_dent_s->file_list_lock_lhb0);
dbgfs_dent_s->parent = parent;
dbgfs_dent_s->self = curr_dent;
strlcpy(dbgfs_dent_s->self_name,
curr, strlen(curr) + 1);
strlcpy(dbgfs_dent_s->par_name, par_dir,
strlen(par_dir) + 1);
mutex_lock(&dent_list_lock_lha0);
list_add_tail(&dbgfs_dent_s->list_node, &dent_list);
mutex_unlock(&dent_list_lock_lha0);
}
} else {
GLINK_DBG("%s:create directory failed for par:curr [%s:%s]\n",
__func__, par_dir, curr);
}
}
/**
* glink_remove_dfs_entry() - remove the the entries from dent_list
* @entry: pointer to the glink_dbgfs_dent structure
*
* This function removes the removes the entries from internally maintained
* linked list of dentries. It also deletes the file list and associated memory
* if present.
*/
void glink_remove_dfs_entry(struct glink_dbgfs_dent *entry)
{
struct glink_dbgfs_data *fentry, *fentry_temp;
unsigned long flags;
if (entry == NULL)
return;
if (!list_empty(&entry->file_list)) {
spin_lock_irqsave(&entry->file_list_lock_lhb0, flags);
list_for_each_entry_safe(fentry, fentry_temp,
&entry->file_list, flist) {
if (fentry->b_priv_free_req)
kfree(fentry->priv_data);
list_del(&fentry->flist);
kfree(fentry);
fentry = NULL;
}
spin_unlock_irqrestore(&entry->file_list_lock_lhb0, flags);
}
list_del(&entry->list_node);
kfree(entry);
entry = NULL;
}
/**
* glink_debugfs_remove_recur() - remove the the directory & files recursively
* @rm_dfs: pointer to the structure glink_dbgfs
*
* This function removes the files & directories below the given directory.
* This also takes care of freeing any memory associated with the debugfs file.
*/
void glink_debugfs_remove_recur(struct glink_dbgfs *rm_dfs)
{
const char *c_dir_name;
const char *p_dir_name;
struct glink_dbgfs_dent *entry, *entry_temp;
struct dentry *par_dent = NULL;
if (rm_dfs == NULL)
return;
c_dir_name = rm_dfs->curr_name;
p_dir_name = rm_dfs->par_name;
mutex_lock(&dent_list_lock_lha0);
list_for_each_entry_safe(entry, entry_temp, &dent_list, list_node) {
if (!strcmp(entry->par_name, c_dir_name)) {
glink_remove_dfs_entry(entry);
} else if (!strcmp(entry->self_name, c_dir_name)
&& !strcmp(entry->par_name, p_dir_name)) {
par_dent = entry->self;
glink_remove_dfs_entry(entry);
}
}
mutex_unlock(&dent_list_lock_lha0);
if (par_dent != NULL)
debugfs_remove_recursive(par_dent);
}
EXPORT_SYMBOL(glink_debugfs_remove_recur);
/**
* glink_debugfs_create() - create the debugfs file
* @name: debugfs file name
* @show: pointer to the actual function which will be invoked upon
* opening this file.
* @dir: pointer to a structure debugfs_dir
* dbgfs_data: pointer to any private data need to be associated with debugfs
* b_free_req: boolean value to decide to free the memory associated with
* @dbgfs_data during deletion of the file
*
* Return: pointer to the file/directory created, NULL in case of error
*
* This function checks which directory will be used to create the debugfs file
* and calls glink_dfs_create_file. Anybody who intend to allocate some memory
* for the dbgfs_data and required to free it in deletion, need to set
* b_free_req to true. Otherwise, there will be a memory leak.
*/
struct dentry *glink_debugfs_create(const char *name,
void (*show)(struct seq_file *),
struct glink_dbgfs *dir, void *dbgfs_data, bool b_free_req)
{
struct dentry *parent = NULL;
struct dentry *dent = NULL;
struct glink_dbgfs_dent *entry;
struct glink_dbgfs_data *file_data;
const char *c_dir_name;
const char *p_dir_name;
unsigned long flags;
if (dir == NULL) {
GLINK_ERR("%s: debugfs_dir strucutre is null\n", __func__);
return NULL;
}
c_dir_name = dir->curr_name;
p_dir_name = dir->par_name;
mutex_lock(&dent_list_lock_lha0);
list_for_each_entry(entry, &dent_list, list_node)
if (!strcmp(entry->par_name, p_dir_name)
&& !strcmp(entry->self_name, c_dir_name)) {
parent = entry->self;
break;
}
mutex_unlock(&dent_list_lock_lha0);
p_dir_name = c_dir_name;
c_dir_name = name;
if (parent != NULL) {
if (dir->b_dir_create) {
dent = debugfs_create_dir(name, parent);
if (dent != NULL)
glink_dfs_update_list(dent, parent,
c_dir_name, p_dir_name);
} else {
file_data = glink_dfs_create_file(name, parent, show,
dbgfs_data, b_free_req);
spin_lock_irqsave(&entry->file_list_lock_lhb0, flags);
if (file_data != NULL)
list_add_tail(&file_data->flist,
&entry->file_list);
spin_unlock_irqrestore(&entry->file_list_lock_lhb0,
flags);
}
} else {
GLINK_DBG("%s: parent dentry is null for [%s]\n",
__func__, name);
}
return dent;
}
EXPORT_SYMBOL(glink_debugfs_create);
/**
* glink_debugfs_init() - initialize the glink debugfs directory structure
*
* Return: 0 in success otherwise appropriate error code
*
* This function initializes the debugfs directory for glink
*/
int glink_debugfs_init(void)
{
struct glink_dbgfs dbgfs;
/* fake parent name */
dent = debugfs_create_dir("glink", NULL);
if (IS_ERR_OR_NULL(dent))
return PTR_ERR(dent);
glink_dfs_update_list(dent, NULL, "glink", "root");
dbgfs.b_dir_create = true;
dbgfs.curr_name = "glink";
dbgfs.par_name = "root";
glink_debugfs_create("xprt", NULL, &dbgfs, NULL, false);
glink_debugfs_create("channel", NULL, &dbgfs, NULL, false);
dbgfs.curr_name = "channel";
dbgfs.par_name = "glink";
dbgfs.b_dir_create = false;
glink_debugfs_create("channels", glink_dfs_create_channel_list,
&dbgfs, NULL, false);
dbgfs.curr_name = "xprt";
glink_debugfs_create("xprts", glink_dfs_create_xprt_list,
&dbgfs, NULL, false);
return 0;
}
EXPORT_SYMBOL(glink_debugfs_init);
/**
* glink_debugfs_exit() - removes the glink debugfs directory
*
* This function recursively remove all the debugfs directories
* starting from dent
*/
void glink_debugfs_exit(void)
{
if (dent != NULL)
debugfs_remove_recursive(dent);
}
EXPORT_SYMBOL(glink_debugfs_exit);
#else
void glink_debugfs_remove_recur(struct glink_dbgfs *dfs) { }
EXPORT_SYMBOL(glink_debugfs_remove_recur);
void glink_debugfs_remove_channel(struct channel_ctx *ch_ctx,
struct glink_core_xprt_ctx *xprt_ctx) { }
EXPORT_SYMBOL(glink_debugfs_remove_channel);
void glink_debugfs_add_channel(struct channel_ctx *ch_ctx,
struct glink_core_xprt_ctx *xprt_ctx) { }
EXPORT_SYMBOL(glink_debugfs_add_channel);
void glink_debugfs_add_xprt(struct glink_core_xprt_ctx *xprt_ctx) { }
EXPORT_SYMBOL(glink_debugfs_add_xprt);
int glink_debugfs_init(void) { return 0; }
EXPORT_SYMBOL(glink_debugfs_init);
void glink_debugfs_exit(void) { }
EXPORT_SYMBOL(glink_debugfs_exit);
#endif /* CONFIG_DEBUG_FS */

View file

@ -0,0 +1,104 @@
/* Copyright (c) 2014-2016, 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 _GLINK_LOOPBACK_COMMANDS_H_
#define _GLINK_LOOPBACK_COMMANDS_H_
#define MAX_NAME_LEN 32
enum request_type {
OPEN = 1,
CLOSE,
QUEUE_RX_INTENT_CONFIG,
TX_CONFIG,
RX_DONE_CONFIG,
};
struct req_hdr {
uint32_t req_id;
uint32_t req_type;
uint32_t req_size;
};
struct open_req {
uint32_t delay_ms;
uint32_t name_len;
char ch_name[MAX_NAME_LEN];
};
struct close_req {
uint32_t delay_ms;
uint32_t name_len;
char ch_name[MAX_NAME_LEN];
};
struct queue_rx_intent_config_req {
uint32_t num_intents;
uint32_t intent_size;
uint32_t random_delay;
uint32_t delay_ms;
uint32_t name_len;
char ch_name[MAX_NAME_LEN];
};
enum transform_type {
NO_TRANSFORM = 0,
PACKET_COUNT,
CHECKSUM,
};
struct tx_config_req {
uint32_t random_delay;
uint32_t delay_ms;
uint32_t echo_count;
uint32_t transform_type;
uint32_t name_len;
char ch_name[MAX_NAME_LEN];
};
struct rx_done_config_req {
uint32_t random_delay;
uint32_t delay_ms;
uint32_t name_len;
char ch_name[MAX_NAME_LEN];
};
union req_payload {
struct open_req open;
struct close_req close;
struct queue_rx_intent_config_req q_rx_int_conf;
struct tx_config_req tx_conf;
struct rx_done_config_req rx_done_conf;
};
struct req {
struct req_hdr hdr;
union req_payload payload;
};
struct resp {
uint32_t req_id;
uint32_t req_type;
uint32_t response;
};
/*
* Tracer Packet Event IDs for Loopback Client/Server.
* This being a client of G-Link, the tracer packet events start
* from 256.
*/
enum loopback_tracer_pkt_events {
LOOPBACK_SRV_TX = 256,
LOOPBACK_SRV_RX = 257,
LOOPBACK_CLNT_TX = 258,
LOOPBACK_CLNT_RX = 259,
};
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,978 @@
/* Copyright (c) 2014-2016, 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/err.h>
#include <linux/ipc_logging.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/random.h>
#include <soc/qcom/glink.h>
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/subsystem_restart.h>
#include "glink_private.h"
#define GLINK_SSR_REPLY_TIMEOUT HZ
#define GLINK_SSR_INTENT_REQ_TIMEOUT_MS 500
#define GLINK_SSR_EVENT_INIT ~0
#define NUM_LOG_PAGES 3
#define GLINK_SSR_LOG(x...) do { \
if (glink_ssr_log_ctx) \
ipc_log_string(glink_ssr_log_ctx, x); \
} while (0)
#define GLINK_SSR_ERR(x...) do { \
pr_err(x); \
GLINK_SSR_LOG(x); \
} while (0)
static void *glink_ssr_log_ctx;
/* Global restart counter */
static uint32_t sequence_number;
/* Flag indicating if responses were received for all SSR notifications */
static bool notifications_successful;
/* Completion for setting notifications_successful */
struct completion notifications_successful_complete;
/**
* struct restart_notifier_block - restart notifier wrapper structure
* subsystem: the name of the subsystem as recognized by the SSR framework
* nb: notifier block structure used by the SSR framework
*/
struct restart_notifier_block {
const char *subsystem;
struct notifier_block nb;
};
/**
* struct configure_and_open_ch_work - Work structure for used for opening
* glink_ssr channels
* edge: The G-Link edge obtained from the link state callback
* transport: The G-Link transport obtained from the link state callback
* link_state: The link state obtained from the link state callback
* ss_info: Subsystem information structure containing the info for this
* callback
* work: Work structure
*/
struct configure_and_open_ch_work {
char edge[GLINK_NAME_SIZE];
char transport[GLINK_NAME_SIZE];
enum glink_link_state link_state;
struct subsys_info *ss_info;
struct work_struct work;
};
/**
* struct close_ch_work - Work structure for used for closing glink_ssr channels
* edge: The G-Link edge name for the channel being closed
* handle: G-Link channel handle to be closed
* work: Work structure
*/
struct close_ch_work {
char edge[GLINK_NAME_SIZE];
void *handle;
struct work_struct work;
};
static int glink_ssr_restart_notifier_cb(struct notifier_block *this,
unsigned long code,
void *data);
static void delete_ss_info_notify_list(struct subsys_info *ss_info);
static int configure_and_open_channel(struct subsys_info *ss_info);
static struct workqueue_struct *glink_ssr_wq;
static LIST_HEAD(subsystem_list);
static atomic_t responses_remaining = ATOMIC_INIT(0);
static wait_queue_head_t waitqueue;
static void link_state_cb_worker(struct work_struct *work)
{
unsigned long flags;
struct configure_and_open_ch_work *ch_open_work =
container_of(work, struct configure_and_open_ch_work, work);
struct subsys_info *ss_info = ch_open_work->ss_info;
GLINK_SSR_LOG("<SSR> %s: LINK STATE[%d] %s:%s\n", __func__,
ch_open_work->link_state, ch_open_work->edge,
ch_open_work->transport);
if (ss_info && ch_open_work->link_state == GLINK_LINK_STATE_UP) {
spin_lock_irqsave(&ss_info->link_up_lock, flags);
if (!ss_info->link_up) {
ss_info->link_up = true;
spin_unlock_irqrestore(&ss_info->link_up_lock, flags);
if (!configure_and_open_channel(ss_info)) {
glink_unregister_link_state_cb(
ss_info->link_state_handle);
ss_info->link_state_handle = NULL;
}
kfree(ch_open_work);
return;
}
spin_unlock_irqrestore(&ss_info->link_up_lock, flags);
} else {
if (ss_info) {
spin_lock_irqsave(&ss_info->link_up_lock, flags);
ss_info->link_up = false;
spin_unlock_irqrestore(&ss_info->link_up_lock, flags);
ss_info->handle = NULL;
} else {
GLINK_SSR_ERR("<SSR> %s: ss_info is NULL\n", __func__);
}
}
kfree(ch_open_work);
}
/**
* glink_lbsrv_link_state_cb() - Callback to receive link state updates
* @cb_info: Information containing link & its state.
* @priv: Private data passed during the link state registration.
*
* This function is called by the G-Link core to notify the glink_ssr module
* regarding the link state updates. This function is registered with the
* G-Link core by the loopback server during glink_register_link_state_cb().
*/
static void glink_ssr_link_state_cb(struct glink_link_state_cb_info *cb_info,
void *priv)
{
struct subsys_info *ss_info;
struct configure_and_open_ch_work *open_ch_work;
if (!cb_info) {
GLINK_SSR_ERR("<SSR> %s: Missing cb_data\n", __func__);
return;
}
ss_info = get_info_for_edge(cb_info->edge);
open_ch_work = kmalloc(sizeof(*open_ch_work), GFP_KERNEL);
if (!open_ch_work) {
GLINK_SSR_ERR("<SSR> %s: Could not allocate open_ch_work\n",
__func__);
return;
}
strlcpy(open_ch_work->edge, cb_info->edge, GLINK_NAME_SIZE);
strlcpy(open_ch_work->transport, cb_info->transport, GLINK_NAME_SIZE);
open_ch_work->link_state = cb_info->link_state;
open_ch_work->ss_info = ss_info;
INIT_WORK(&open_ch_work->work, link_state_cb_worker);
queue_work(glink_ssr_wq, &open_ch_work->work);
}
/**
* glink_ssr_notify_rx() - RX Notification callback
* @handle: G-Link channel handle
* @priv: Private callback data
* @pkt_priv: Private packet data
* @ptr: Pointer to the data received
* @size: Size of the data received
*
* This function is a notification callback from the G-Link core that data
* has been received from the remote side. This data is validate to make
* sure it is a cleanup_done message and is processed accordingly if it is.
*/
void glink_ssr_notify_rx(void *handle, const void *priv, const void *pkt_priv,
const void *ptr, size_t size)
{
struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv;
struct cleanup_done_msg *resp = (struct cleanup_done_msg *)ptr;
if (unlikely(!cb_data))
goto missing_cb_data;
if (unlikely(!cb_data->do_cleanup_data))
goto missing_do_cleanup_data;
if (unlikely(!resp))
goto missing_response;
if (unlikely(resp->version != cb_data->do_cleanup_data->version))
goto version_mismatch;
if (unlikely(resp->seq_num != cb_data->do_cleanup_data->seq_num))
goto invalid_seq_number;
if (unlikely(resp->response != GLINK_SSR_CLEANUP_DONE))
goto wrong_response;
cb_data->responded = true;
atomic_dec(&responses_remaining);
GLINK_SSR_LOG(
"<SSR> %s: Response from %s resp[%d] version[%d] seq_num[%d] restarted[%s]\n",
__func__, cb_data->edge, resp->response,
resp->version, resp->seq_num,
cb_data->do_cleanup_data->name);
kfree(cb_data->do_cleanup_data);
cb_data->do_cleanup_data = NULL;
wake_up(&waitqueue);
return;
missing_cb_data:
panic("%s: Missing cb_data!\n", __func__);
return;
missing_do_cleanup_data:
panic("%s: Missing do_cleanup data!\n", __func__);
return;
missing_response:
GLINK_SSR_ERR("<SSR> %s: Missing response data\n", __func__);
return;
version_mismatch:
GLINK_SSR_ERR("<SSR> %s: Version mismatch. %s[%d], %s[%d]\n", __func__,
"do_cleanup version", cb_data->do_cleanup_data->version,
"cleanup_done version", resp->version);
return;
invalid_seq_number:
GLINK_SSR_ERR("<SSR> %s: Invalid seq. number. %s[%d], %s[%d]\n",
__func__, "do_cleanup seq num",
cb_data->do_cleanup_data->seq_num,
"cleanup_done seq_num", resp->seq_num);
return;
wrong_response:
GLINK_SSR_ERR("<SSR> %s: Not a cleaup_done message. %s[%d]\n", __func__,
"cleanup_done response", resp->response);
}
/**
* glink_ssr_notify_tx_done() - Transmit finished notification callback
* @handle: G-Link channel handle
* @priv: Private callback data
* @pkt_priv: Private packet data
* @ptr: Pointer to the data received
*
* This function is a notification callback from the G-Link core that data
* we sent has finished transmitting.
*/
void glink_ssr_notify_tx_done(void *handle, const void *priv,
const void *pkt_priv, const void *ptr)
{
struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv;
if (unlikely(!cb_data)) {
panic("%s: cb_data is NULL!\n", __func__);
return;
}
GLINK_SSR_LOG("<SSR> %s: Notified %s of restart\n",
__func__, cb_data->edge);
cb_data->tx_done = true;
}
void close_ch_worker(struct work_struct *work)
{
unsigned long flags;
void *link_state_handle;
struct subsys_info *ss_info;
struct close_ch_work *close_work =
container_of(work, struct close_ch_work, work);
glink_close(close_work->handle);
ss_info = get_info_for_edge(close_work->edge);
if (WARN_ON(!ss_info))
return;
spin_lock_irqsave(&ss_info->link_up_lock, flags);
ss_info->link_up = false;
spin_unlock_irqrestore(&ss_info->link_up_lock, flags);
if (WARN_ON(ss_info->link_state_handle != NULL))
return;
link_state_handle = glink_register_link_state_cb(ss_info->link_info,
NULL);
if (IS_ERR_OR_NULL(link_state_handle))
GLINK_SSR_ERR("<SSR> %s: %s, ret[%d]\n", __func__,
"Couldn't register link state cb",
(int)PTR_ERR(link_state_handle));
else
ss_info->link_state_handle = link_state_handle;
if (WARN_ON(!ss_info->cb_data))
return;
kfree(ss_info->cb_data);
kfree(close_work);
}
/**
* glink_ssr_notify_state() - Channel state notification callback
* @handle: G-Link channel handle
* @priv: Private callback data
* @event: The state that has been transitioned to
*
* This function is a notification callback from the G-Link core that the
* channel state has changed.
*/
void glink_ssr_notify_state(void *handle, const void *priv, unsigned int event)
{
struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv;
struct close_ch_work *close_work;
if (!cb_data) {
GLINK_SSR_ERR("<SSR> %s: Could not allocate data for cb_data\n",
__func__);
} else {
GLINK_SSR_LOG("<SSR> %s: event[%d]\n",
__func__, event);
cb_data->event = event;
if (event == GLINK_REMOTE_DISCONNECTED) {
close_work =
kmalloc(sizeof(struct close_ch_work),
GFP_KERNEL);
if (!close_work) {
GLINK_SSR_ERR(
"<SSR> %s: Could not allocate %s\n",
__func__, "close work");
return;
}
strlcpy(close_work->edge, cb_data->edge,
sizeof(close_work->edge));
close_work->handle = handle;
INIT_WORK(&close_work->work, close_ch_worker);
queue_work(glink_ssr_wq, &close_work->work);
}
}
}
/**
* glink_ssr_notify_rx_intent_req() - RX intent request notification callback
* @handle: G-Link channel handle
* @priv: Private callback data
* @req_size: The size of the requested intent
*
* This function is a notification callback from the G-Link core of the remote
* side's request for an RX intent to be queued.
*
* Return: Boolean indicating whether or not the request was successfully
* received
*/
bool glink_ssr_notify_rx_intent_req(void *handle, const void *priv,
size_t req_size)
{
struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv;
if (!cb_data) {
GLINK_SSR_ERR("<SSR> %s: Could not allocate data for cb_data\n",
__func__);
return false;
}
GLINK_SSR_LOG("<SSR> %s: rx_intent_req of size %zu\n",
__func__, req_size);
return true;
}
/**
* glink_ssr_restart_notifier_cb() - SSR restart notifier callback function
* @this: Notifier block used by the SSR framework
* @code: The SSR code for which stage of restart is occurring
* @data: Structure containing private data - not used here.
*
* This function is a callback for the SSR framework. From here we initiate
* our handling of SSR.
*
* Return: Status of SSR handling
*/
static int glink_ssr_restart_notifier_cb(struct notifier_block *this,
unsigned long code,
void *data)
{
int ret = 0;
struct subsys_info *ss_info = NULL;
struct restart_notifier_block *notifier =
container_of(this, struct restart_notifier_block, nb);
if (code == SUBSYS_AFTER_SHUTDOWN) {
GLINK_SSR_LOG("<SSR> %s: %s: subsystem restart for %s\n",
__func__, "SUBSYS_AFTER_SHUTDOWN",
notifier->subsystem);
ss_info = get_info_for_subsystem(notifier->subsystem);
if (ss_info == NULL) {
GLINK_SSR_ERR("<SSR> %s: ss_info is NULL\n", __func__);
return -EINVAL;
}
glink_ssr(ss_info->edge);
ret = notify_for_subsystem(ss_info);
if (ret) {
GLINK_SSR_ERR("<SSR>: %s: %s, ret[%d]\n", __func__,
"Subsystem notification failed", ret);
return ret;
}
}
return NOTIFY_DONE;
}
/**
* notify for subsystem() - Notify other subsystems that a subsystem is being
* restarted
* @ss_info: Subsystem info structure for the subsystem being restarted
*
* This function sends notifications to affected subsystems that the subsystem
* in ss_info is being restarted, and waits for the cleanup done response from
* all of those subsystems. It also initiates any local cleanup that is
* necessary.
*
* Return: 0 on success, standard error codes otherwise
*/
int notify_for_subsystem(struct subsys_info *ss_info)
{
struct subsys_info *ss_info_channel;
struct subsys_info_leaf *ss_leaf_entry;
struct do_cleanup_msg *do_cleanup_data;
void *handle;
int wait_ret;
int ret;
unsigned long flags;
if (!ss_info) {
GLINK_SSR_ERR("<SSR> %s: ss_info structure invalid\n",
__func__);
return -EINVAL;
}
/*
* No locking is needed here because ss_info->notify_list_len is
* only modified during setup.
*/
atomic_set(&responses_remaining, ss_info->notify_list_len);
init_waitqueue_head(&waitqueue);
notifications_successful = true;
list_for_each_entry(ss_leaf_entry, &ss_info->notify_list,
notify_list_node) {
GLINK_SSR_LOG(
"<SSR> %s: Notifying: %s:%s of %s restart, seq_num[%d]\n",
__func__, ss_leaf_entry->edge,
ss_leaf_entry->xprt, ss_info->edge,
sequence_number);
ss_info_channel =
get_info_for_subsystem(ss_leaf_entry->ssr_name);
if (ss_info_channel == NULL) {
GLINK_SSR_ERR(
"<SSR> %s: unable to find subsystem name\n",
__func__);
return -ENODEV;
}
handle = ss_info_channel->handle;
ss_leaf_entry->cb_data = ss_info_channel->cb_data;
spin_lock_irqsave(&ss_info->link_up_lock, flags);
if (IS_ERR_OR_NULL(ss_info_channel->handle) ||
!ss_info_channel->cb_data ||
!ss_info_channel->link_up ||
ss_info_channel->cb_data->event
!= GLINK_CONNECTED) {
GLINK_SSR_LOG(
"<SSR> %s: %s:%s %s[%d], %s[%p], %s[%d]\n",
__func__, ss_leaf_entry->edge, "Not connected",
"resp. remaining",
atomic_read(&responses_remaining), "handle",
ss_info_channel->handle, "link_up",
ss_info_channel->link_up);
spin_unlock_irqrestore(&ss_info->link_up_lock, flags);
atomic_dec(&responses_remaining);
continue;
}
spin_unlock_irqrestore(&ss_info->link_up_lock, flags);
do_cleanup_data = kmalloc(sizeof(struct do_cleanup_msg),
GFP_KERNEL);
if (!do_cleanup_data) {
GLINK_SSR_ERR(
"%s %s: Could not allocate do_cleanup_msg\n",
"<SSR>", __func__);
return -ENOMEM;
}
do_cleanup_data->version = 0;
do_cleanup_data->command = GLINK_SSR_DO_CLEANUP;
do_cleanup_data->seq_num = sequence_number;
do_cleanup_data->name_len = strlen(ss_info->edge);
strlcpy(do_cleanup_data->name, ss_info->edge,
do_cleanup_data->name_len + 1);
ss_leaf_entry->cb_data->do_cleanup_data = do_cleanup_data;
ret = glink_queue_rx_intent(handle,
(void *)ss_leaf_entry->cb_data,
sizeof(struct cleanup_done_msg));
if (ret) {
GLINK_SSR_ERR(
"%s %s: %s, ret[%d], resp. remaining[%d]\n",
"<SSR>", __func__,
"queue_rx_intent failed", ret,
atomic_read(&responses_remaining));
kfree(do_cleanup_data);
ss_leaf_entry->cb_data->do_cleanup_data = NULL;
if (strcmp(ss_leaf_entry->ssr_name, "rpm")) {
subsystem_restart(ss_leaf_entry->ssr_name);
ss_leaf_entry->restarted = true;
} else {
panic("%s: Could not queue intent for RPM!\n",
__func__);
}
atomic_dec(&responses_remaining);
continue;
}
if (strcmp(ss_leaf_entry->ssr_name, "rpm"))
ret = glink_tx(handle, ss_leaf_entry->cb_data,
do_cleanup_data,
sizeof(*do_cleanup_data),
GLINK_TX_REQ_INTENT);
else
ret = glink_tx(handle, ss_leaf_entry->cb_data,
do_cleanup_data,
sizeof(*do_cleanup_data),
GLINK_TX_SINGLE_THREADED);
if (ret) {
GLINK_SSR_ERR("<SSR> %s: tx failed, ret[%d], %s[%d]\n",
__func__, ret, "resp. remaining",
atomic_read(&responses_remaining));
kfree(do_cleanup_data);
ss_leaf_entry->cb_data->do_cleanup_data = NULL;
if (strcmp(ss_leaf_entry->ssr_name, "rpm")) {
subsystem_restart(ss_leaf_entry->ssr_name);
ss_leaf_entry->restarted = true;
} else {
panic("%s: glink_tx() to RPM failed!\n",
__func__);
}
atomic_dec(&responses_remaining);
continue;
}
sequence_number++;
}
wait_ret = wait_event_timeout(waitqueue,
atomic_read(&responses_remaining) == 0,
GLINK_SSR_REPLY_TIMEOUT);
list_for_each_entry(ss_leaf_entry, &ss_info->notify_list,
notify_list_node) {
if (!wait_ret && !IS_ERR_OR_NULL(ss_leaf_entry->cb_data)
&& !ss_leaf_entry->cb_data->responded) {
GLINK_SSR_ERR("%s %s: Subsystem %s %s\n",
"<SSR>", __func__, ss_leaf_entry->edge,
"failed to respond. Restarting.");
notifications_successful = false;
/* Check for RPM, as it can't be restarted */
if (!strcmp(ss_leaf_entry->ssr_name, "rpm"))
panic("%s: RPM failed to respond!\n", __func__);
else if (!ss_leaf_entry->restarted)
subsystem_restart(ss_leaf_entry->ssr_name);
}
ss_leaf_entry->restarted = false;
if (!IS_ERR_OR_NULL(ss_leaf_entry->cb_data))
ss_leaf_entry->cb_data->responded = false;
}
complete(&notifications_successful_complete);
return 0;
}
EXPORT_SYMBOL(notify_for_subsystem);
/**
* configure_and_open_channel() - configure and open a G-Link channel for
* the given subsystem
* @ss_info: The subsys_info structure where the channel will be stored
*
* Return: 0 on success, standard error codes otherwise
*/
static int configure_and_open_channel(struct subsys_info *ss_info)
{
struct glink_open_config open_cfg;
struct ssr_notify_data *cb_data = NULL;
void *handle = NULL;
if (!ss_info) {
GLINK_SSR_ERR("<SSR> %s: ss_info structure invalid\n",
__func__);
return -EINVAL;
}
cb_data = kmalloc(sizeof(struct ssr_notify_data), GFP_KERNEL);
if (!cb_data) {
GLINK_SSR_ERR("<SSR> %s: Could not allocate cb_data\n",
__func__);
return -ENOMEM;
}
cb_data->responded = false;
cb_data->event = GLINK_SSR_EVENT_INIT;
cb_data->edge = ss_info->edge;
ss_info->cb_data = cb_data;
memset(&open_cfg, 0, sizeof(struct glink_open_config));
if (ss_info->xprt) {
open_cfg.transport = ss_info->xprt;
} else {
open_cfg.transport = NULL;
open_cfg.options = GLINK_OPT_INITIAL_XPORT;
}
open_cfg.edge = ss_info->edge;
open_cfg.name = "glink_ssr";
open_cfg.notify_rx = glink_ssr_notify_rx;
open_cfg.notify_tx_done = glink_ssr_notify_tx_done;
open_cfg.notify_state = glink_ssr_notify_state;
open_cfg.notify_rx_intent_req = glink_ssr_notify_rx_intent_req;
open_cfg.priv = ss_info->cb_data;
open_cfg.rx_intent_req_timeout_ms = GLINK_SSR_INTENT_REQ_TIMEOUT_MS;
handle = glink_open(&open_cfg);
if (IS_ERR_OR_NULL(handle)) {
GLINK_SSR_ERR(
"<SSR> %s:%s %s: unable to open channel, ret[%d]\n",
open_cfg.edge, open_cfg.name, __func__,
(int)PTR_ERR(handle));
kfree(cb_data);
cb_data = NULL;
ss_info->cb_data = NULL;
return PTR_ERR(handle);
}
ss_info->handle = handle;
return 0;
}
/**
* get_info_for_subsystem() - Retrieve information about a subsystem from the
* global subsystem_info_list
* @subsystem: The name of the subsystem recognized by the SSR
* framework
*
* Return: subsys_info structure containing info for the requested subsystem;
* NULL if no structure can be found for the requested subsystem
*/
struct subsys_info *get_info_for_subsystem(const char *subsystem)
{
struct subsys_info *ss_info_entry;
list_for_each_entry(ss_info_entry, &subsystem_list,
subsystem_list_node) {
if (!strcmp(subsystem, ss_info_entry->ssr_name))
return ss_info_entry;
}
return NULL;
}
EXPORT_SYMBOL(get_info_for_subsystem);
/**
* get_info_for_edge() - Retrieve information about a subsystem from the
* global subsystem_info_list
* @edge: The name of the edge recognized by G-Link
*
* Return: subsys_info structure containing info for the requested subsystem;
* NULL if no structure can be found for the requested subsystem
*/
struct subsys_info *get_info_for_edge(const char *edge)
{
struct subsys_info *ss_info_entry;
list_for_each_entry(ss_info_entry, &subsystem_list,
subsystem_list_node) {
if (!strcmp(edge, ss_info_entry->edge))
return ss_info_entry;
}
return NULL;
}
EXPORT_SYMBOL(get_info_for_edge);
/**
* glink_ssr_get_seq_num() - Get the current SSR sequence number
*
* Return: The current SSR sequence number
*/
uint32_t glink_ssr_get_seq_num(void)
{
return sequence_number;
}
EXPORT_SYMBOL(glink_ssr_get_seq_num);
/**
* delete_ss_info_notify_list() - Delete the notify list for a subsystem
* @ss_info: The subsystem info structure
*/
static void delete_ss_info_notify_list(struct subsys_info *ss_info)
{
struct subsys_info_leaf *leaf, *temp;
list_for_each_entry_safe(leaf, temp, &ss_info->notify_list,
notify_list_node) {
list_del(&leaf->notify_list_node);
kfree(leaf);
}
}
/**
* glink_ssr_wait_cleanup_done() - Get the value of the
* notifications_successful flag.
* @timeout_multiplier: timeout multiplier for waiting on all processors
*
* Return: True if cleanup_done received from all processors, false otherwise
*/
bool glink_ssr_wait_cleanup_done(unsigned int ssr_timeout_multiplier)
{
int wait_ret =
wait_for_completion_timeout(&notifications_successful_complete,
ssr_timeout_multiplier * GLINK_SSR_REPLY_TIMEOUT);
reinit_completion(&notifications_successful_complete);
if (!notifications_successful || !wait_ret)
return false;
else
return true;
}
EXPORT_SYMBOL(glink_ssr_wait_cleanup_done);
/**
* glink_ssr_probe() - G-Link SSR platform device probe function
* @pdev: Pointer to the platform device structure
*
* This function parses DT for information on which subsystems should be
* notified when each subsystem undergoes SSR. The global subsystem information
* list is built from this information. In addition, SSR notifier callback
* functions are registered here for the necessary subsystems.
*
* Return: 0 on success, standard error codes otherwise
*/
static int glink_ssr_probe(struct platform_device *pdev)
{
struct device_node *node;
struct device_node *phandle_node;
struct restart_notifier_block *nb;
struct subsys_info *ss_info;
struct subsys_info_leaf *ss_info_leaf;
struct glink_link_info *link_info;
char *key;
const char *edge;
const char *subsys_name;
const char *xprt;
void *handle;
void *link_state_handle;
int phandle_index = 0;
int ret = 0;
if (!pdev) {
GLINK_SSR_ERR("<SSR> %s: pdev is NULL\n", __func__);
ret = -EINVAL;
goto pdev_null_or_ss_info_alloc_failed;
}
node = pdev->dev.of_node;
ss_info = kmalloc(sizeof(*ss_info), GFP_KERNEL);
if (!ss_info) {
GLINK_SSR_ERR("<SSR> %s: %s\n", __func__,
"Could not allocate subsystem info structure\n");
ret = -ENOMEM;
goto pdev_null_or_ss_info_alloc_failed;
}
INIT_LIST_HEAD(&ss_info->notify_list);
link_info = kmalloc(sizeof(struct glink_link_info),
GFP_KERNEL);
if (!link_info) {
GLINK_SSR_ERR("<SSR> %s: %s\n", __func__,
"Could not allocate link info structure\n");
ret = -ENOMEM;
goto link_info_alloc_failed;
}
ss_info->link_info = link_info;
key = "label";
subsys_name = of_get_property(node, key, NULL);
if (!subsys_name) {
GLINK_SSR_ERR("<SSR> %s: missing key %s\n", __func__, key);
ret = -ENODEV;
goto label_or_edge_missing;
}
key = "qcom,edge";
edge = of_get_property(node, key, NULL);
if (!edge) {
GLINK_SSR_ERR("<SSR> %s: missing key %s\n", __func__, key);
ret = -ENODEV;
goto label_or_edge_missing;
}
key = "qcom,xprt";
xprt = of_get_property(node, key, NULL);
if (!xprt)
GLINK_SSR_LOG(
"%s %s: no transport present for subys/edge %s/%s\n",
"<SSR>", __func__, subsys_name, edge);
ss_info->ssr_name = subsys_name;
ss_info->edge = edge;
ss_info->xprt = xprt;
ss_info->notify_list_len = 0;
ss_info->link_info->transport = xprt;
ss_info->link_info->edge = edge;
ss_info->link_info->glink_link_state_notif_cb = glink_ssr_link_state_cb;
ss_info->link_up = false;
ss_info->handle = NULL;
ss_info->link_state_handle = NULL;
ss_info->cb_data = NULL;
spin_lock_init(&ss_info->link_up_lock);
nb = kmalloc(sizeof(struct restart_notifier_block), GFP_KERNEL);
if (!nb) {
GLINK_SSR_ERR("<SSR> %s: Could not allocate notifier block\n",
__func__);
ret = -ENOMEM;
goto label_or_edge_missing;
}
nb->subsystem = subsys_name;
nb->nb.notifier_call = glink_ssr_restart_notifier_cb;
handle = subsys_notif_register_notifier(nb->subsystem, &nb->nb);
if (IS_ERR_OR_NULL(handle)) {
GLINK_SSR_ERR("<SSR> %s: Could not register SSR notifier cb\n",
__func__);
ret = -EINVAL;
goto nb_registration_fail;
}
key = "qcom,notify-edges";
while (true) {
phandle_node = of_parse_phandle(node, key, phandle_index++);
if (!phandle_node && phandle_index == 0) {
GLINK_SSR_ERR(
"<SSR> %s: qcom,notify-edges is not present",
__func__);
ret = -ENODEV;
goto notify_edges_not_present;
}
if (!phandle_node)
break;
ss_info_leaf = kmalloc(sizeof(struct subsys_info_leaf),
GFP_KERNEL);
if (!ss_info_leaf) {
GLINK_SSR_ERR(
"<SSR> %s: Could not allocate subsys_info_leaf\n",
__func__);
ret = -ENOMEM;
goto notify_edges_not_present;
}
subsys_name = of_get_property(phandle_node, "label", NULL);
edge = of_get_property(phandle_node, "qcom,edge", NULL);
xprt = of_get_property(phandle_node, "qcom,xprt", NULL);
of_node_put(phandle_node);
if (!subsys_name || !edge) {
GLINK_SSR_ERR(
"%s, %s: Found DT node with invalid data!\n",
"<SSR>", __func__);
ret = -EINVAL;
goto invalid_dt_node;
}
ss_info_leaf->ssr_name = subsys_name;
ss_info_leaf->edge = edge;
ss_info_leaf->xprt = xprt;
ss_info_leaf->restarted = false;
list_add_tail(&ss_info_leaf->notify_list_node,
&ss_info->notify_list);
ss_info->notify_list_len++;
}
list_add_tail(&ss_info->subsystem_list_node, &subsystem_list);
link_state_handle = glink_register_link_state_cb(ss_info->link_info,
NULL);
if (IS_ERR_OR_NULL(link_state_handle)) {
GLINK_SSR_ERR("<SSR> %s: Could not register link state cb\n",
__func__);
ret = PTR_ERR(link_state_handle);
goto link_state_register_fail;
}
ss_info->link_state_handle = link_state_handle;
return 0;
link_state_register_fail:
list_del(&ss_info->subsystem_list_node);
invalid_dt_node:
kfree(ss_info_leaf);
notify_edges_not_present:
subsys_notif_unregister_notifier(handle, &nb->nb);
delete_ss_info_notify_list(ss_info);
nb_registration_fail:
kfree(nb);
label_or_edge_missing:
kfree(link_info);
link_info_alloc_failed:
kfree(ss_info);
pdev_null_or_ss_info_alloc_failed:
return ret;
}
static const struct of_device_id match_table[] = {
{ .compatible = "qcom,glink_ssr" },
{},
};
static struct platform_driver glink_ssr_driver = {
.probe = glink_ssr_probe,
.driver = {
.name = "msm_glink_ssr",
.owner = THIS_MODULE,
.of_match_table = match_table,
},
};
static int glink_ssr_init(void)
{
int ret;
glink_ssr_log_ctx =
ipc_log_context_create(NUM_LOG_PAGES, "glink_ssr", 0);
glink_ssr_wq = create_singlethread_workqueue("glink_ssr_wq");
ret = platform_driver_register(&glink_ssr_driver);
if (ret)
GLINK_SSR_ERR("<SSR> %s: %s ret: %d\n", __func__,
"glink_ssr driver registration failed", ret);
notifications_successful = false;
init_completion(&notifications_successful_complete);
return 0;
}
module_init(glink_ssr_init);
MODULE_DESCRIPTION("MSM Generic Link (G-Link) SSR Module");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,204 @@
/* Copyright (c) 2014-2016, 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 _SOC_QCOM_GLINK_XPRT_IF_H_
#define _SOC_QCOM_GLINK_XPRT_IF_H_
#include <linux/bitops.h>
#include <linux/list.h>
#include <linux/types.h>
struct glink_core_xprt_ctx;
struct glink_core_if;
struct channel_ctx;
struct glink_core_rx_intent;
enum buf_type {
LINEAR = 0,
VECTOR,
};
enum xprt_ids {
SMEM_XPRT_ID = 100,
SPIV2_XPRT_ID = SMEM_XPRT_ID,
SMD_TRANS_XPRT_ID = 200,
LLOOP_XPRT_ID = 300,
MOCK_XPRT_HIGH_ID = 390,
MOCK_XPRT_ID = 400,
MOCK_XPRT_LOW_ID = 410,
};
#define GCAP_SIGNALS BIT(0)
#define GCAP_INTENTLESS BIT(1)
#define GCAP_TRACER_PKT BIT(2)
#define GCAP_AUTO_QUEUE_RX_INT BIT(3)
/**
* struct glink_core_tx_pkt - Transmit Packet information
* @list_done: Index to the channel's transmit queue.
* @list_done: Index to the channel's acknowledgment queue.
* @pkt_priv: Private information specific to the packet.
* @data: Pointer to the buffer containing the data.
* @riid: Remote receive intent used to transmit the packet.
* @rcid: Remote channel receiving the packet.
* @size: Total size of the data in the packet.
* @tx_len: Data length to transmit in the current transmit slot.
* @size_remaining: Remaining size of the data in the packet.
* @intent_size: Receive intent size queued by the remote side.
* @tracer_pkt: Flag to indicate if the packet is a tracer packet.
* @iovec: Pointer to the vector buffer packet.
* @vprovider: Packet-specific virtual buffer provider function.
* @pprovider: Packet-specific physical buffer provider function.
* @cookie: Transport-specific cookie
* @pkt_ref: Active references to the packet.
*/
struct glink_core_tx_pkt {
struct list_head list_node;
struct list_head list_done;
const void *pkt_priv;
const void *data;
uint32_t riid;
uint32_t rcid;
uint32_t size;
uint32_t tx_len;
uint32_t size_remaining;
size_t intent_size;
bool tracer_pkt;
void *iovec;
void * (*vprovider)(void *iovec, size_t offset, size_t *size);
void * (*pprovider)(void *iovec, size_t offset, size_t *size);
void *cookie;
struct rwref_lock pkt_ref;
};
/**
* Note - each call to register the interface must pass a unique
* instance of this data.
*/
struct glink_transport_if {
/* Negotiation */
void (*tx_cmd_version)(struct glink_transport_if *if_ptr,
uint32_t version,
uint32_t features);
void (*tx_cmd_version_ack)(struct glink_transport_if *if_ptr,
uint32_t version,
uint32_t features);
uint32_t (*set_version)(struct glink_transport_if *if_ptr,
uint32_t version,
uint32_t features);
/* channel state */
int (*tx_cmd_ch_open)(struct glink_transport_if *if_ptr, uint32_t lcid,
const char *name, uint16_t req_xprt);
int (*tx_cmd_ch_close)(struct glink_transport_if *if_ptr,
uint32_t lcid);
void (*tx_cmd_ch_remote_open_ack)(struct glink_transport_if *if_ptr,
uint32_t rcid, uint16_t xprt_resp);
void (*tx_cmd_ch_remote_close_ack)(struct glink_transport_if *if_ptr,
uint32_t rcid);
int (*ssr)(struct glink_transport_if *if_ptr);
/* channel data */
int (*allocate_rx_intent)(struct glink_transport_if *if_ptr,
size_t size,
struct glink_core_rx_intent *intent);
int (*deallocate_rx_intent)(struct glink_transport_if *if_ptr,
struct glink_core_rx_intent *intent);
/* Optional */
int (*reuse_rx_intent)(struct glink_transport_if *if_ptr,
struct glink_core_rx_intent *intent);
int (*tx_cmd_local_rx_intent)(struct glink_transport_if *if_ptr,
uint32_t lcid, size_t size, uint32_t liid);
void (*tx_cmd_local_rx_done)(struct glink_transport_if *if_ptr,
uint32_t lcid, uint32_t liid, bool reuse);
int (*tx)(struct glink_transport_if *if_ptr, uint32_t lcid,
struct glink_core_tx_pkt *pctx);
int (*tx_cmd_rx_intent_req)(struct glink_transport_if *if_ptr,
uint32_t lcid, size_t size);
int (*tx_cmd_remote_rx_intent_req_ack)(
struct glink_transport_if *if_ptr,
uint32_t lcid, bool granted);
int (*tx_cmd_set_sigs)(struct glink_transport_if *if_ptr,
uint32_t lcid, uint32_t sigs);
/* Optional. If NULL at xprt registration, dummies will be used */
int (*poll)(struct glink_transport_if *if_ptr, uint32_t lcid);
int (*mask_rx_irq)(struct glink_transport_if *if_ptr, uint32_t lcid,
bool mask, void *pstruct);
int (*wait_link_down)(struct glink_transport_if *if_ptr);
int (*tx_cmd_tracer_pkt)(struct glink_transport_if *if_ptr,
uint32_t lcid, struct glink_core_tx_pkt *pctx);
unsigned long (*get_power_vote_ramp_time)(
struct glink_transport_if *if_ptr, uint32_t state);
int (*power_vote)(struct glink_transport_if *if_ptr, uint32_t state);
int (*power_unvote)(struct glink_transport_if *if_ptr);
/*
* Keep data pointers at the end of the structure after all function
* pointer to allow for in-place initialization.
*/
/* private pointer for core */
struct glink_core_xprt_ctx *glink_core_priv;
/* core pointer (set during transport registration) */
struct glink_core_if *glink_core_if_ptr;
};
#ifdef CONFIG_MSM_GLINK
/**
* get_tx_vaddr() - Get the virtual address from which the tx has to be done
* @pctx: transmit packet context.
* @offset: offset into the packet.
* @tx_size: pointer to hold the length of the contiguous buffer
* space.
*
* Return: Address from which the tx has to be done.
*/
static inline void *get_tx_vaddr(struct glink_core_tx_pkt *pctx, size_t offset,
size_t *tx_size)
{
void *pdata;
if (pctx->vprovider) {
return pctx->vprovider((void *)pctx->iovec, offset, tx_size);
} else if (pctx->pprovider) {
pdata = pctx->pprovider((void *)pctx->iovec, offset, tx_size);
return phys_to_virt((unsigned long)pdata);
}
return NULL;
}
/**
* glink_xprt_name_to_id() - convert transport name to id
* @name: Name of the transport.
* @id: Assigned id.
*
* Return: 0 on success or standard Linux error code.
*/
int glink_xprt_name_to_id(const char *name, uint16_t *id);
#else /* CONFIG_MSM_GLINK */
static inline void *get_tx_vaddr(struct glink_core_tx_pkt *pctx, size_t offset,
size_t *tx_size)
{
return NULL;
}
static inline int glink_xprt_name_to_id(const char *name, uint16_t *id)
{
return -ENODEV;
}
#endif /* CONFIG_MSM_GLINK */
#endif /* _SOC_QCOM_GLINK_XPRT_IF_H_ */

View file

@ -0,0 +1,255 @@
/* Copyright (c) 2015-2016, 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 <asm/arch_timer.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/types.h>
#include <soc/qcom/tracer_pkt.h>
#define CREATE_TRACE_POINTS
#include "tracer_pkt_private.h"
static unsigned int qdss_tracing;
module_param_named(qdss_tracing_enable, qdss_tracing,
uint, S_IRUGO | S_IWUSR | S_IWGRP);
#define TRACER_PKT_VERSION 1
#define MAX_CC_WLEN 3
#define HEX_DUMP_HDR "Tracer Packet:"
/**
* struct tracer_pkt_hdr - data structure defiining the tracer packet header
* @version: Tracer Packet version.
* @reserved: Reserved fields in the tracer packet.
* @id_valid: Indicates the presence of a subsytem & transport ID.
* @qdss_tracing: Enable the event logging to QDSS.
* @ccl: Client cookie/private information length in words.
* @pkt_len: Length of the tracer packet in words.
* @pkt_offset: Offset into the packet to log events, in words.
* @clnt_event_cfg: Client-specific event configuration bit mask.
* @glink_event_cfg: G-Link-specific event configuration bit mask.
* @base_ts: Base timestamp when the tracer packet is initialized.
* @cc: Client cookie/private information.
*/
struct tracer_pkt_hdr {
uint16_t version:4;
uint16_t reserved:8;
uint16_t id_valid:1;
uint16_t qdss_tracing:1;
uint16_t ccl:2;
uint16_t pkt_len;
uint16_t pkt_offset;
uint16_t clnt_event_cfg;
uint32_t glink_event_cfg;
u64 base_ts;
uint32_t cc[MAX_CC_WLEN];
} __attribute__((__packed__));
/**
* struct tracer_pkt_event - data structure defining the tracer packet event
* @event_id: Event ID.
* @event_ts: Timestamp at which the event occurred.
*/
struct tracer_pkt_event {
uint32_t event_id;
uint32_t event_ts;
};
/**
* tracer_pkt_init() - initialize the tracer packet
* @data: Pointer to the buffer to be initialized with a tracer
* packet.
* @data_len: Length of the buffer.
* @client_event_cfg: Client-specific event configuration mask.
* @glink_event_cfg: G-Link-specific event configuration mask.
* @pkt_priv: Private/Cookie information to be added to the tracer
* packet.
* @pkt_priv_len: Length of the private data.
*
* This function is used to initialize a buffer with the tracer packet header.
* The tracer packet header includes the data as passed by the elements in the
* parameters.
*
* Return: 0 on success, standard Linux error codes on failure.
*/
int tracer_pkt_init(void *data, size_t data_len,
uint16_t client_event_cfg, uint32_t glink_event_cfg,
void *pkt_priv, size_t pkt_priv_len)
{
struct tracer_pkt_hdr *pkt_hdr;
if (!data || !data_len)
return -EINVAL;
if (!IS_ALIGNED(data_len, sizeof(uint32_t)))
return -EINVAL;
if (data_len < sizeof(*pkt_hdr))
return -ETOOSMALL;
pkt_hdr = (struct tracer_pkt_hdr *)data;
pkt_hdr->version = TRACER_PKT_VERSION;
pkt_hdr->reserved = 0;
pkt_hdr->id_valid = 0;
pkt_hdr->qdss_tracing = qdss_tracing ? true : false;
if (pkt_priv_len > MAX_CC_WLEN * sizeof(uint32_t))
pkt_hdr->ccl = MAX_CC_WLEN;
else
pkt_hdr->ccl = pkt_priv_len/sizeof(uint32_t) +
(pkt_priv_len & (sizeof(uint32_t) - 1) ? 1 : 0);
pkt_hdr->pkt_len = data_len / sizeof(uint32_t);
pkt_hdr->pkt_offset = sizeof(*pkt_hdr) / sizeof(uint32_t);
pkt_hdr->clnt_event_cfg = client_event_cfg;
pkt_hdr->glink_event_cfg = glink_event_cfg;
pkt_hdr->base_ts = arch_counter_get_cntvct();
memcpy(pkt_hdr->cc, pkt_priv, pkt_hdr->ccl * sizeof(uint32_t));
return 0;
}
EXPORT_SYMBOL(tracer_pkt_init);
/**
* tracer_pkt_set_event_cfg() - set the event configuration mask in the tracer
* packet
* @data: Pointer to the buffer to be initialized with event
* configuration mask.
* @client_event_cfg: Client-specific event configuration mask.
* @glink_event_cfg: G-Link-specific event configuration mask.
*
* This function is used to initialize a buffer with the event configuration
* mask as passed by the elements in the parameters.
*
* Return: 0 on success, standard Linux error codes on failure.
*/
int tracer_pkt_set_event_cfg(void *data, uint16_t client_event_cfg,
uint32_t glink_event_cfg)
{
struct tracer_pkt_hdr *pkt_hdr;
if (!data)
return -EINVAL;
pkt_hdr = (struct tracer_pkt_hdr *)data;
if (unlikely(pkt_hdr->version != TRACER_PKT_VERSION))
return -EINVAL;
pkt_hdr->clnt_event_cfg = client_event_cfg;
pkt_hdr->glink_event_cfg = glink_event_cfg;
return 0;
}
EXPORT_SYMBOL(tracer_pkt_set_event_cfg);
/**
* tracer_pkt_log_event() - log an event specific to the tracer packet
* @data: Pointer to the buffer containing tracer packet.
* @event_id: Event ID to be logged.
*
* This function is used to log an event specific to the tracer packet.
* The event is logged either into the tracer packet itself or a different
* tracing mechanism as configured.
*
* Return: 0 on success, standard Linux error codes on failure.
*/
int tracer_pkt_log_event(void *data, uint32_t event_id)
{
struct tracer_pkt_hdr *pkt_hdr;
struct tracer_pkt_event event;
if (!data)
return -EINVAL;
pkt_hdr = (struct tracer_pkt_hdr *)data;
if (unlikely(pkt_hdr->version != TRACER_PKT_VERSION))
return -EINVAL;
if (qdss_tracing) {
trace_tracer_pkt_event(event_id, pkt_hdr->cc);
return 0;
}
if (unlikely((pkt_hdr->pkt_len - pkt_hdr->pkt_offset) *
sizeof(uint32_t) < sizeof(event)))
return -ETOOSMALL;
event.event_id = event_id;
event.event_ts = (uint32_t)arch_counter_get_cntvct();
memcpy(data + (pkt_hdr->pkt_offset * sizeof(uint32_t)),
&event, sizeof(event));
pkt_hdr->pkt_offset += sizeof(event)/sizeof(uint32_t);
return 0;
}
EXPORT_SYMBOL(tracer_pkt_log_event);
/**
* tracer_pkt_calc_hex_dump_size() - calculate the hex dump size of a tracer
* packet
* @data: Pointer to the buffer containing tracer packet.
* @data_len: Length of the tracer packet buffer.
*
* This function is used to calculate the length of the buffer required to
* hold the hex dump of the tracer packet.
*
* Return: 0 on success, standard Linux error codes on failure.
*/
size_t tracer_pkt_calc_hex_dump_size(void *data, size_t data_len)
{
size_t hex_dump_size;
struct tracer_pkt_hdr *pkt_hdr;
if (!data || data_len <= 0)
return -EINVAL;
pkt_hdr = (struct tracer_pkt_hdr *)data;
if (unlikely(pkt_hdr->version != TRACER_PKT_VERSION))
return -EINVAL;
/*
* Hex Dump Prefix + newline
* 0x<first_word> + newline
* ...
* 0x<last_word> + newline + null-termination character.
*/
hex_dump_size = strlen(HEX_DUMP_HDR) + 1 + (pkt_hdr->pkt_len * 11) + 1;
return hex_dump_size;
}
EXPORT_SYMBOL(tracer_pkt_calc_hex_dump_size);
/**
* tracer_pkt_hex_dump() - hex dump the tracer packet into a buffer
* @buf: Buffer to contain the hex dump of the tracer packet.
* @buf_len: Length of the hex dump buffer.
* @data: Buffer containing the tracer packet.
* @data_len: Length of the buffer containing the tracer packet.
*
* This function is used to dump the contents of the tracer packet into
* a buffer in a specific hexadecimal format. The hex dump buffer can then
* be dumped through debugfs.
*
* Return: 0 on success, standard Linux error codes on failure.
*/
int tracer_pkt_hex_dump(void *buf, size_t buf_len, void *data, size_t data_len)
{
int i, j = 0;
char *dst = (char *)buf;
if (!buf || buf_len <= 0 || !data || data_len <= 0)
return -EINVAL;
if (buf_len < tracer_pkt_calc_hex_dump_size(data, data_len))
return -EINVAL;
j = scnprintf(dst, buf_len, "%s\n", HEX_DUMP_HDR);
for (i = 0; i < data_len/sizeof(uint32_t); i++)
j += scnprintf(dst + j, buf_len - j, "0x%08x\n",
*((uint32_t *)data + i));
dst[j] = '\0';
return 0;
}
EXPORT_SYMBOL(tracer_pkt_hex_dump);

Some files were not shown because too many files have changed in this diff Show more