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:
parent
7e4fa652ce
commit
fa6135ecbe
24 changed files with 18441 additions and 0 deletions
1239
Documentation/arm/msm/glink.txt
Normal file
1239
Documentation/arm/msm/glink.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
44
Documentation/devicetree/bindings/arm/msm/glink_spi_xprt.txt
Normal file
44
Documentation/devicetree/bindings/arm/msm/glink_spi_xprt.txt
Normal 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>;
|
||||
};
|
||||
36
Documentation/devicetree/bindings/arm/msm/glink_ssr.txt
Normal file
36
Documentation/devicetree/bindings/arm/msm/glink_ssr.txt
Normal 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>;
|
||||
};
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
6036
drivers/soc/qcom/glink.c
Normal file
File diff suppressed because it is too large
Load diff
218
drivers/soc/qcom/glink_core_if.h
Normal file
218
drivers/soc/qcom/glink_core_if.h
Normal 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_ */
|
||||
788
drivers/soc/qcom/glink_debugfs.c
Normal file
788
drivers/soc/qcom/glink_debugfs.c
Normal 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 */
|
||||
104
drivers/soc/qcom/glink_loopback_commands.h
Normal file
104
drivers/soc/qcom/glink_loopback_commands.h
Normal 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
|
||||
1297
drivers/soc/qcom/glink_loopback_server.c
Normal file
1297
drivers/soc/qcom/glink_loopback_server.c
Normal file
File diff suppressed because it is too large
Load diff
1099
drivers/soc/qcom/glink_private.h
Normal file
1099
drivers/soc/qcom/glink_private.h
Normal file
File diff suppressed because it is too large
Load diff
3077
drivers/soc/qcom/glink_smem_native_xprt.c
Normal file
3077
drivers/soc/qcom/glink_smem_native_xprt.c
Normal file
File diff suppressed because it is too large
Load diff
2194
drivers/soc/qcom/glink_spi_xprt.c
Normal file
2194
drivers/soc/qcom/glink_spi_xprt.c
Normal file
File diff suppressed because it is too large
Load diff
978
drivers/soc/qcom/glink_ssr.c
Normal file
978
drivers/soc/qcom/glink_ssr.c
Normal 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(¬ifications_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(¬ifications_successful_complete,
|
||||
ssr_timeout_multiplier * GLINK_SSR_REPLY_TIMEOUT);
|
||||
reinit_completion(¬ifications_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(¬ifications_successful_complete);
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(glink_ssr_init);
|
||||
|
||||
MODULE_DESCRIPTION("MSM Generic Link (G-Link) SSR Module");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
204
drivers/soc/qcom/glink_xprt_if.h
Normal file
204
drivers/soc/qcom/glink_xprt_if.h
Normal 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_ */
|
||||
255
drivers/soc/qcom/tracer_pkt.c
Normal file
255
drivers/soc/qcom/tracer_pkt.c
Normal 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
Loading…
Reference in a new issue