All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 00/12] net: introduce Qualcomm IPA driver
@ 2018-11-07  0:32 ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas, robh+dt, mark.rutland
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid

This series presents the driver for the Qualcomm IP Accelerator (IPA).
The IPA is a hardware component present in some Qualcomm SoCs that
allows network functions--such as routing, filtering, network address
translation and aggregation--to be performed without active involvement
of the main application processor (AP).

Initially, these advanced features are not supported; the IPA driver
simply provides a network interface that makes the modem's LTE
network available to the AP.  In addition, support is only provided
for the IPA found in the Qualcomm SDM845 SoC.

This code is derived from a driver developed internally by Qualcomm.
A version of the original source can be seen here:
    https://source.codeaurora.org/quic/la/kernel/msm-4.9/tree
in the "drivers/platform/msm/ipa" directory.  Many were involved in
developing this, but the following individuals deserve explicit
acknowledgement for their substantial contributions:

    Abhishek Choubey
    Ady Abraham
    Chaitanya Pratapa
    David Arinzon
    Ghanim Fodi
    Gidon Studinski
    Ravi Gummadidala
    Shihuan Liu
    Skylar Chang

The code has undergone considerable rework to prepare it for
incorporation into upstream Linux.  Parts of it bear little
resemblance to the original driver.  Still, some work remains
to be done.  The current code and its design had a preliminary
review, and some changes to the data path implementation were
recommended.   These have not yet been addressed:
- Use NAPI for all interfaces, not just RX (and WAN data) endpoints.
- Do more work in the NAPI poll function, including collecting
  completed TX requests and posting buffers for RX.
- Do not use periodic NOP requests as a way to avoid TX interrupts.
- The NAPI context should be associated with the hardware interrupt
  (it is now associated with something abstracted from the hardware).
- Use threaded interrupts, to avoid the need for using spinlocks and
  atomic variables for synchronizing between workqueue and interrupt
  context.
- Have runtime power management enable and disable IPA clock and
  interconnects.
Many thanks to Arnd Bergmann, Ilias Apalodimas, and Bjorn Andersson
for their early feedback.

While there clearly remains work to do on this, we felt that things
are far enough along that it would be helpful to solicit broader
input on the code.  Major issues are best addressed as soon as
possible, and even minor issues when identified help in setting
priorities.

This code is dependent on the following two sets of code, which have
been posted for review but are not yet accepted upstream:
- Interconnect framework:  https://lkml.org/lkml/2018/8/31/444
- SDM845 interconnect provider driver:  https://lkml.org/lkml/2018/8/24/25

In addition, it depends on four more bits of code that have not yet
been posted for upstream review, but are expected to be available soon:
- clk-rpmh support for IPA from David Dai <daidavid1@codeaurora.org>
- SDM845 reserved memory from Bjorn Andersson <bjorn.andersson@linaro.org>
- list_cut_end() from Alex Elder <elder@linaro.org>
- FIELD_MAX() in "bitfield.h" from Alex Elder <elder@linaro.org>

This code (including its dependencies) is available in buildable
form here, based on kernel v4.19:
    remote: ssh://git@git.linaro.org/people/alex.elder/linux.git
    branch: qualcomm_ipa-v1
	    59562facd61a arm64: dts: sdm845: add IPA information

					-Alex

Alex Elder (12):
  dt-bindings: soc: qcom: add IPA bindings
  soc: qcom: ipa: DMA helpers
  soc: qcom: ipa: generic software interface
  soc: qcom: ipa: immediate commands
  soc: qcom: ipa: IPA interrupts and the microcontroller
  soc: qcom: ipa: QMI modem communication
  soc: qcom: ipa: IPA register abstraction
  soc: qcom: ipa: utility functions
  soc: qcom: ipa: main IPA source file
  soc: qcom: ipa: data path
  soc: qcom: ipa: IPA rmnet interface
  soc: qcom: ipa: build and "ipa_i.h"

 .../devicetree/bindings/soc/qcom/qcom,ipa.txt |  136 ++
 .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |   15 +
 drivers/net/ipa/Kconfig                       |   30 +
 drivers/net/ipa/Makefile                      |    7 +
 drivers/net/ipa/gsi.c                         | 1685 ++++++++++++++
 drivers/net/ipa/gsi.h                         |  195 ++
 drivers/net/ipa/gsi_reg.h                     |  563 +++++
 drivers/net/ipa/ipa_dma.c                     |   61 +
 drivers/net/ipa/ipa_dma.h                     |   61 +
 drivers/net/ipa/ipa_dp.c                      | 1994 +++++++++++++++++
 drivers/net/ipa/ipa_i.h                       |  573 +++++
 drivers/net/ipa/ipa_interrupts.c              |  307 +++
 drivers/net/ipa/ipa_main.c                    | 1400 ++++++++++++
 drivers/net/ipa/ipa_qmi.c                     |  406 ++++
 drivers/net/ipa/ipa_qmi.h                     |   12 +
 drivers/net/ipa/ipa_qmi_msg.c                 |  587 +++++
 drivers/net/ipa/ipa_qmi_msg.h                 |  233 ++
 drivers/net/ipa/ipa_reg.c                     |  972 ++++++++
 drivers/net/ipa/ipa_reg.h                     |  614 +++++
 drivers/net/ipa/ipa_uc.c                      |  336 +++
 drivers/net/ipa/ipa_utils.c                   | 1035 +++++++++
 drivers/net/ipa/ipahal.c                      |  541 +++++
 drivers/net/ipa/ipahal.h                      |  253 +++
 drivers/net/ipa/msm_rmnet.h                   |  120 +
 drivers/net/ipa/rmnet_config.h                |   31 +
 drivers/net/ipa/rmnet_ipa.c                   |  805 +++++++
 26 files changed, 12972 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
 create mode 100644 drivers/net/ipa/Kconfig
 create mode 100644 drivers/net/ipa/Makefile
 create mode 100644 drivers/net/ipa/gsi.c
 create mode 100644 drivers/net/ipa/gsi.h
 create mode 100644 drivers/net/ipa/gsi_reg.h
 create mode 100644 drivers/net/ipa/ipa_dma.c
 create mode 100644 drivers/net/ipa/ipa_dma.h
 create mode 100644 drivers/net/ipa/ipa_dp.c
 create mode 100644 drivers/net/ipa/ipa_i.h
 create mode 100644 drivers/net/ipa/ipa_interrupts.c
 create mode 100644 drivers/net/ipa/ipa_main.c
 create mode 100644 drivers/net/ipa/ipa_qmi.c
 create mode 100644 drivers/net/ipa/ipa_qmi.h
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.c
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.h
 create mode 100644 drivers/net/ipa/ipa_reg.c
 create mode 100644 drivers/net/ipa/ipa_reg.h
 create mode 100644 drivers/net/ipa/ipa_uc.c
 create mode 100644 drivers/net/ipa/ipa_utils.c
 create mode 100644 drivers/net/ipa/ipahal.c
 create mode 100644 drivers/net/ipa/ipahal.h
 create mode 100644 drivers/net/ipa/msm_rmnet.h
 create mode 100644 drivers/net/ipa/rmnet_config.h
 create mode 100644 drivers/net/ipa/rmnet_ipa.c

-- 
2.17.1

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 00/12] net: introduce Qualcomm IPA driver
@ 2018-11-07  0:32 ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This series presents the driver for the Qualcomm IP Accelerator (IPA).
The IPA is a hardware component present in some Qualcomm SoCs that
allows network functions--such as routing, filtering, network address
translation and aggregation--to be performed without active involvement
of the main application processor (AP).

Initially, these advanced features are not supported; the IPA driver
simply provides a network interface that makes the modem's LTE
network available to the AP.  In addition, support is only provided
for the IPA found in the Qualcomm SDM845 SoC.

This code is derived from a driver developed internally by Qualcomm.
A version of the original source can be seen here:
    https://source.codeaurora.org/quic/la/kernel/msm-4.9/tree
in the "drivers/platform/msm/ipa" directory.  Many were involved in
developing this, but the following individuals deserve explicit
acknowledgement for their substantial contributions:

    Abhishek Choubey
    Ady Abraham
    Chaitanya Pratapa
    David Arinzon
    Ghanim Fodi
    Gidon Studinski
    Ravi Gummadidala
    Shihuan Liu
    Skylar Chang

The code has undergone considerable rework to prepare it for
incorporation into upstream Linux.  Parts of it bear little
resemblance to the original driver.  Still, some work remains
to be done.  The current code and its design had a preliminary
review, and some changes to the data path implementation were
recommended.   These have not yet been addressed:
- Use NAPI for all interfaces, not just RX (and WAN data) endpoints.
- Do more work in the NAPI poll function, including collecting
  completed TX requests and posting buffers for RX.
- Do not use periodic NOP requests as a way to avoid TX interrupts.
- The NAPI context should be associated with the hardware interrupt
  (it is now associated with something abstracted from the hardware).
- Use threaded interrupts, to avoid the need for using spinlocks and
  atomic variables for synchronizing between workqueue and interrupt
  context.
- Have runtime power management enable and disable IPA clock and
  interconnects.
Many thanks to Arnd Bergmann, Ilias Apalodimas, and Bjorn Andersson
for their early feedback.

While there clearly remains work to do on this, we felt that things
are far enough along that it would be helpful to solicit broader
input on the code.  Major issues are best addressed as soon as
possible, and even minor issues when identified help in setting
priorities.

This code is dependent on the following two sets of code, which have
been posted for review but are not yet accepted upstream:
- Interconnect framework:  https://lkml.org/lkml/2018/8/31/444
- SDM845 interconnect provider driver:  https://lkml.org/lkml/2018/8/24/25

In addition, it depends on four more bits of code that have not yet
been posted for upstream review, but are expected to be available soon:
- clk-rpmh support for IPA from David Dai <daidavid1@codeaurora.org>
- SDM845 reserved memory from Bjorn Andersson <bjorn.andersson@linaro.org>
- list_cut_end() from Alex Elder <elder@linaro.org>
- FIELD_MAX() in "bitfield.h" from Alex Elder <elder@linaro.org>

This code (including its dependencies) is available in buildable
form here, based on kernel v4.19:
    remote: ssh://git at git.linaro.org/people/alex.elder/linux.git
    branch: qualcomm_ipa-v1
	    59562facd61a arm64: dts: sdm845: add IPA information

					-Alex

Alex Elder (12):
  dt-bindings: soc: qcom: add IPA bindings
  soc: qcom: ipa: DMA helpers
  soc: qcom: ipa: generic software interface
  soc: qcom: ipa: immediate commands
  soc: qcom: ipa: IPA interrupts and the microcontroller
  soc: qcom: ipa: QMI modem communication
  soc: qcom: ipa: IPA register abstraction
  soc: qcom: ipa: utility functions
  soc: qcom: ipa: main IPA source file
  soc: qcom: ipa: data path
  soc: qcom: ipa: IPA rmnet interface
  soc: qcom: ipa: build and "ipa_i.h"

 .../devicetree/bindings/soc/qcom/qcom,ipa.txt |  136 ++
 .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |   15 +
 drivers/net/ipa/Kconfig                       |   30 +
 drivers/net/ipa/Makefile                      |    7 +
 drivers/net/ipa/gsi.c                         | 1685 ++++++++++++++
 drivers/net/ipa/gsi.h                         |  195 ++
 drivers/net/ipa/gsi_reg.h                     |  563 +++++
 drivers/net/ipa/ipa_dma.c                     |   61 +
 drivers/net/ipa/ipa_dma.h                     |   61 +
 drivers/net/ipa/ipa_dp.c                      | 1994 +++++++++++++++++
 drivers/net/ipa/ipa_i.h                       |  573 +++++
 drivers/net/ipa/ipa_interrupts.c              |  307 +++
 drivers/net/ipa/ipa_main.c                    | 1400 ++++++++++++
 drivers/net/ipa/ipa_qmi.c                     |  406 ++++
 drivers/net/ipa/ipa_qmi.h                     |   12 +
 drivers/net/ipa/ipa_qmi_msg.c                 |  587 +++++
 drivers/net/ipa/ipa_qmi_msg.h                 |  233 ++
 drivers/net/ipa/ipa_reg.c                     |  972 ++++++++
 drivers/net/ipa/ipa_reg.h                     |  614 +++++
 drivers/net/ipa/ipa_uc.c                      |  336 +++
 drivers/net/ipa/ipa_utils.c                   | 1035 +++++++++
 drivers/net/ipa/ipahal.c                      |  541 +++++
 drivers/net/ipa/ipahal.h                      |  253 +++
 drivers/net/ipa/msm_rmnet.h                   |  120 +
 drivers/net/ipa/rmnet_config.h                |   31 +
 drivers/net/ipa/rmnet_ipa.c                   |  805 +++++++
 26 files changed, 12972 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
 create mode 100644 drivers/net/ipa/Kconfig
 create mode 100644 drivers/net/ipa/Makefile
 create mode 100644 drivers/net/ipa/gsi.c
 create mode 100644 drivers/net/ipa/gsi.h
 create mode 100644 drivers/net/ipa/gsi_reg.h
 create mode 100644 drivers/net/ipa/ipa_dma.c
 create mode 100644 drivers/net/ipa/ipa_dma.h
 create mode 100644 drivers/net/ipa/ipa_dp.c
 create mode 100644 drivers/net/ipa/ipa_i.h
 create mode 100644 drivers/net/ipa/ipa_interrupts.c
 create mode 100644 drivers/net/ipa/ipa_main.c
 create mode 100644 drivers/net/ipa/ipa_qmi.c
 create mode 100644 drivers/net/ipa/ipa_qmi.h
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.c
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.h
 create mode 100644 drivers/net/ipa/ipa_reg.c
 create mode 100644 drivers/net/ipa/ipa_reg.h
 create mode 100644 drivers/net/ipa/ipa_uc.c
 create mode 100644 drivers/net/ipa/ipa_utils.c
 create mode 100644 drivers/net/ipa/ipahal.c
 create mode 100644 drivers/net/ipa/ipahal.h
 create mode 100644 drivers/net/ipa/msm_rmnet.h
 create mode 100644 drivers/net/ipa/rmnet_config.h
 create mode 100644 drivers/net/ipa/rmnet_ipa.c

-- 
2.17.1

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: robh+dt, mark.rutland, davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid

Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
device tree nodes.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
 .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
 2 files changed, 151 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt

diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
new file mode 100644
index 000000000000..d4d3d37df029
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
@@ -0,0 +1,136 @@
+Qualcomm IPA (IP Accelerator) Driver
+
+This binding describes the Qualcomm IPA.  The IPA is capable of offloading
+certain network processing tasks (e.g. filtering, routing, and NAT) from
+the main processor.  The IPA currently serves only as a network interface,
+providing access to an LTE network available via a modem.
+
+The IPA sits between multiple independent "execution environments,"
+including the AP subsystem (APSS) and the modem.  The IPA presents
+a Generic Software Interface (GSI) to each execution environment.
+The GSI is an integral part of the IPA, but it is logically isolated
+and has a distinct interrupt and a separately-defined address space.
+
+    ----------   -------------   ---------
+    |        |   |G|       |G|   |       |
+    |  APSS  |===|S|  IPA  |S|===| Modem |
+    |        |   |I|       |I|   |       |
+    ----------   -------------   ---------
+
+See also:
+  bindings/interrupt-controller/interrupts.txt
+  bindings/interconnect/interconnect.txt
+  bindings/soc/qcom/qcom,smp2p.txt
+  bindings/reserved-memory/reserved-memory.txt
+  bindings/clock/clock-bindings.txt
+
+All properties defined below are required.
+
+- compatible:
+	Must be one of the following compatible strings:
+		"qcom,ipa-sdm845-modem_init"
+		"qcom,ipa-sdm845-tz_init"
+
+-reg:
+	Resources specyfing the physical address spaces of the IPA and GSI.
+
+-reg-names:
+	The names of the address space ranges defined by the "reg" property.
+	Must be "ipa" and "gsi".
+
+- interrupts-extended:
+	Specifies the IRQs used by the IPA.  Four cells are required,
+	specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
+	from the modem; and the "ready for stage 2 initialization"
+	interrupt from the modem.  The first two are hardware IRQs; the
+	third and fourth are SMP2P input interrupts.
+
+- interrupt-names:
+	The names of the interrupts defined by the "interrupts-extended"
+	property.  Must be "ipa", "gsi", "ipa-clock-query", and
+	"ipa-post-init".
+
+- clocks:
+	Resource that defines the IPA core clock.
+
+- clock-names:
+	The name used for the IPA core clock.  Must be "core".
+
+- interconnects:
+	Specifies the interconnects used by the IPA.  Three cells are
+	required, specifying:  the path from the IPA to memory; from
+	IPA to internal (SoC resident) memory; and between the AP
+	subsystem and IPA for register access.
+
+- interconnect-names:
+	The names of the interconnects defined by the "interconnects"
+	property.  Must be "memory", "imem", and "config".
+
+- qcom,smem-states
+	The state bits used for SMP2P output.  Two cells must be specified.
+	The first indicates whether the value in the second bit is valid
+	(1 means valid).  The second, if valid, defines whether the IPA
+	clock is enabled (1 means enabled).
+
+- qcom,smem-state-names
+	The names of the state bits used for SMP2P output.  These must be
+	"ipa-clock-enabled-valid" and "ipa-clock-enabled".
+
+- memory-region
+	A phandle for a reserved memory area that holds the firmware passed
+	to Trust Zone for authentication.  (Note, this is required
+	only for "qcom,ipa-sdm845-tz_init".)
+
+= EXAMPLE
+
+The following example represents the IPA present in the SDM845 SoC.  It
+shows portions of the "modem-smp2p" node to indicate its relationship
+with the interrupts and SMEM states used by the IPA.
+
+	modem-smp2p {
+		compatible = "qcom,smp2p";
+		. . .
+		ipa_smp2p_out: ipa-ap-to-modem {
+			qcom,entry-name = "ipa";
+			#qcom,smem-state-cells = <1>;
+		};
+
+		ipa_smp2p_in: ipa-modem-to-ap {
+			qcom,entry-name = "ipa";
+			interrupt-controller;
+			#interrupt-cells = <2>;
+		};
+	};
+
+	ipa@1e00000 {
+		compatible = "qcom,ipa-sdm845-modem_init";
+
+		reg = <0x1e40000 0x34000>,
+		      <0x1e04000 0x2c000>;
+		reg-names = "ipa",
+			    "gsi";
+
+		interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
+				      <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
+				      <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
+				      <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
+		interrupt-names = "ipa",
+				  "gsi",
+				  "ipa-clock-query",
+				  "ipa-post-init";
+
+		clocks = <&rpmhcc RPMH_IPA_CLK>;
+		clock-names = "core";
+
+		interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
+			        <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
+			        <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
+		interconnect-names = "memory",
+				     "imem",
+				     "config";
+
+		qcom,smem-states = <&ipa_smp2p_out 0>,
+				   <&ipa_smp2p_out 1>;
+		qcom,smem-state-names = "ipa-clock-enabled-valid",
+					"ipa-clock-enabled";
+	};
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
new file mode 100644
index 000000000000..3d0b2aabefc7
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
@@ -0,0 +1,15 @@
+Qualcomm IPA RMNet Driver
+
+This binding describes the IPA RMNet driver, which is used to
+represent virtual interfaces available on the modem accessed via
+the IPA.  Other than the compatible string there are no properties
+associated with this device.
+
+- compatible:
+	Must be "qcom,rmnet-ipa".
+
+= EXAMPLE
+
+	qcom,rmnet-ipa {
+		compatible = "qcom,rmnet-ipa";
+	};
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
device tree nodes.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
 .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
 2 files changed, 151 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt

diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
new file mode 100644
index 000000000000..d4d3d37df029
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
@@ -0,0 +1,136 @@
+Qualcomm IPA (IP Accelerator) Driver
+
+This binding describes the Qualcomm IPA.  The IPA is capable of offloading
+certain network processing tasks (e.g. filtering, routing, and NAT) from
+the main processor.  The IPA currently serves only as a network interface,
+providing access to an LTE network available via a modem.
+
+The IPA sits between multiple independent "execution environments,"
+including the AP subsystem (APSS) and the modem.  The IPA presents
+a Generic Software Interface (GSI) to each execution environment.
+The GSI is an integral part of the IPA, but it is logically isolated
+and has a distinct interrupt and a separately-defined address space.
+
+    ----------   -------------   ---------
+    |        |   |G|       |G|   |       |
+    |  APSS  |===|S|  IPA  |S|===| Modem |
+    |        |   |I|       |I|   |       |
+    ----------   -------------   ---------
+
+See also:
+  bindings/interrupt-controller/interrupts.txt
+  bindings/interconnect/interconnect.txt
+  bindings/soc/qcom/qcom,smp2p.txt
+  bindings/reserved-memory/reserved-memory.txt
+  bindings/clock/clock-bindings.txt
+
+All properties defined below are required.
+
+- compatible:
+	Must be one of the following compatible strings:
+		"qcom,ipa-sdm845-modem_init"
+		"qcom,ipa-sdm845-tz_init"
+
+-reg:
+	Resources specyfing the physical address spaces of the IPA and GSI.
+
+-reg-names:
+	The names of the address space ranges defined by the "reg" property.
+	Must be "ipa" and "gsi".
+
+- interrupts-extended:
+	Specifies the IRQs used by the IPA.  Four cells are required,
+	specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
+	from the modem; and the "ready for stage 2 initialization"
+	interrupt from the modem.  The first two are hardware IRQs; the
+	third and fourth are SMP2P input interrupts.
+
+- interrupt-names:
+	The names of the interrupts defined by the "interrupts-extended"
+	property.  Must be "ipa", "gsi", "ipa-clock-query", and
+	"ipa-post-init".
+
+- clocks:
+	Resource that defines the IPA core clock.
+
+- clock-names:
+	The name used for the IPA core clock.  Must be "core".
+
+- interconnects:
+	Specifies the interconnects used by the IPA.  Three cells are
+	required, specifying:  the path from the IPA to memory; from
+	IPA to internal (SoC resident) memory; and between the AP
+	subsystem and IPA for register access.
+
+- interconnect-names:
+	The names of the interconnects defined by the "interconnects"
+	property.  Must be "memory", "imem", and "config".
+
+- qcom,smem-states
+	The state bits used for SMP2P output.  Two cells must be specified.
+	The first indicates whether the value in the second bit is valid
+	(1 means valid).  The second, if valid, defines whether the IPA
+	clock is enabled (1 means enabled).
+
+- qcom,smem-state-names
+	The names of the state bits used for SMP2P output.  These must be
+	"ipa-clock-enabled-valid" and "ipa-clock-enabled".
+
+- memory-region
+	A phandle for a reserved memory area that holds the firmware passed
+	to Trust Zone for authentication.  (Note, this is required
+	only for "qcom,ipa-sdm845-tz_init".)
+
+= EXAMPLE
+
+The following example represents the IPA present in the SDM845 SoC.  It
+shows portions of the "modem-smp2p" node to indicate its relationship
+with the interrupts and SMEM states used by the IPA.
+
+	modem-smp2p {
+		compatible = "qcom,smp2p";
+		. . .
+		ipa_smp2p_out: ipa-ap-to-modem {
+			qcom,entry-name = "ipa";
+			#qcom,smem-state-cells = <1>;
+		};
+
+		ipa_smp2p_in: ipa-modem-to-ap {
+			qcom,entry-name = "ipa";
+			interrupt-controller;
+			#interrupt-cells = <2>;
+		};
+	};
+
+	ipa at 1e00000 {
+		compatible = "qcom,ipa-sdm845-modem_init";
+
+		reg = <0x1e40000 0x34000>,
+		      <0x1e04000 0x2c000>;
+		reg-names = "ipa",
+			    "gsi";
+
+		interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
+				      <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
+				      <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
+				      <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
+		interrupt-names = "ipa",
+				  "gsi",
+				  "ipa-clock-query",
+				  "ipa-post-init";
+
+		clocks = <&rpmhcc RPMH_IPA_CLK>;
+		clock-names = "core";
+
+		interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
+			        <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
+			        <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
+		interconnect-names = "memory",
+				     "imem",
+				     "config";
+
+		qcom,smem-states = <&ipa_smp2p_out 0>,
+				   <&ipa_smp2p_out 1>;
+		qcom,smem-state-names = "ipa-clock-enabled-valid",
+					"ipa-clock-enabled";
+	};
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
new file mode 100644
index 000000000000..3d0b2aabefc7
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
@@ -0,0 +1,15 @@
+Qualcomm IPA RMNet Driver
+
+This binding describes the IPA RMNet driver, which is used to
+represent virtual interfaces available on the modem accessed via
+the IPA.  Other than the compatible string there are no properties
+associated with this device.
+
+- compatible:
+	Must be "qcom,rmnet-ipa".
+
+= EXAMPLE
+
+	qcom,rmnet-ipa {
+		compatible = "qcom,rmnet-ipa";
+	};
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This patch includes code implementing the IPA DMA module, which
defines a structure to represent a DMA allocation for the IPA device.
It's used throughout the IPA code.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_dma.c | 61 +++++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_dma.h | 61 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 122 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_dma.c
 create mode 100644 drivers/net/ipa/ipa_dma.h

diff --git a/drivers/net/ipa/ipa_dma.c b/drivers/net/ipa/ipa_dma.c
new file mode 100644
index 000000000000..dfde59e5072a
--- /dev/null
+++ b/drivers/net/ipa/ipa_dma.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/string.h>
+
+#include "ipa_dma.h"
+
+static struct device *ipa_dma_dev;
+
+int ipa_dma_init(struct device *dev, u32 align)
+{
+	int ret;
+
+	/* Ensure DMA addresses will have the alignment we require */
+	if (dma_get_cache_alignment() % align)
+		return -ENOTSUPP;
+
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (!ret)
+		ipa_dma_dev = dev;
+
+	return ret;
+}
+
+void ipa_dma_exit(void)
+{
+	ipa_dma_dev = NULL;
+}
+
+int ipa_dma_alloc(struct ipa_dma_mem *mem, size_t size, gfp_t gfp)
+{
+	dma_addr_t phys;
+	void *virt;
+
+	virt = dma_zalloc_coherent(ipa_dma_dev, size, &phys, gfp);
+	if (!virt)
+		return -ENOMEM;
+
+	mem->virt = virt;
+	mem->phys = phys;
+	mem->size = size;
+
+	return 0;
+}
+
+void ipa_dma_free(struct ipa_dma_mem *mem)
+{
+	dma_free_coherent(ipa_dma_dev, mem->size, mem->virt, mem->phys);
+	memset(mem, 0, sizeof(*mem));
+}
+
+void *ipa_dma_phys_to_virt(struct ipa_dma_mem *mem, dma_addr_t phys)
+{
+	return mem->virt + (phys - mem->phys);
+}
diff --git a/drivers/net/ipa/ipa_dma.h b/drivers/net/ipa/ipa_dma.h
new file mode 100644
index 000000000000..e211dbd9d4ec
--- /dev/null
+++ b/drivers/net/ipa/ipa_dma.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_DMA_H_
+#define _IPA_DMA_H_
+
+#include <linux/types.h>
+#include <linux/device.h>
+
+/**
+ * struct ipa_dma_mem - IPA allocated DMA memory descriptor
+ * @virt: host virtual base address of allocated DMA memory
+ * @phys: bus physical base address of DMA memory
+ * @size: size (bytes) of DMA memory
+ */
+struct ipa_dma_mem {
+	void *virt;
+	dma_addr_t phys;
+	size_t size;
+};
+
+/**
+ * ipa_dma_init() - Initialize IPA DMA system.
+ * @dev:	IPA device structure
+ * @align:	Hardware required alignment for DMA memory
+ *
+ * Returns:	 0 if successful, or a negative error code.
+ */
+int ipa_dma_init(struct device *dev, u32 align);
+
+/**
+ * ipa_dma_exit() - shut down/clean up IPA DMA system
+ */
+void ipa_dma_exit(void);
+
+/**
+ * ipa_dma_alloc() - allocate a DMA buffer, describe it in mem struct
+ * @mem:	Memory structure to fill with allocation information.
+ * @size:	Size of DMA buffer to allocate.
+ * @gfp:	Allocation mode.
+ */
+int ipa_dma_alloc(struct ipa_dma_mem *mem, size_t size, gfp_t gfp);
+
+/**
+ * ipa_dma_free() - free a previously-allocated DMA buffer
+ * @mem:	Information about DMA allocation to free
+ */
+void ipa_dma_free(struct ipa_dma_mem *mem);
+
+/**
+ * ipa_dma_phys_to_virt() - return the virtual equivalent of a DMA address
+ * @phys:	DMA allocation information
+ * @phys:	Physical address to convert
+ *
+ * Return:	Virtual address corresponding to the given physical address
+ */
+void *ipa_dma_phys_to_virt(struct ipa_dma_mem *mem, dma_addr_t phys);
+
+#endif /* !_IPA_DMA_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch includes code implementing the IPA DMA module, which
defines a structure to represent a DMA allocation for the IPA device.
It's used throughout the IPA code.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_dma.c | 61 +++++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_dma.h | 61 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 122 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_dma.c
 create mode 100644 drivers/net/ipa/ipa_dma.h

diff --git a/drivers/net/ipa/ipa_dma.c b/drivers/net/ipa/ipa_dma.c
new file mode 100644
index 000000000000..dfde59e5072a
--- /dev/null
+++ b/drivers/net/ipa/ipa_dma.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/string.h>
+
+#include "ipa_dma.h"
+
+static struct device *ipa_dma_dev;
+
+int ipa_dma_init(struct device *dev, u32 align)
+{
+	int ret;
+
+	/* Ensure DMA addresses will have the alignment we require */
+	if (dma_get_cache_alignment() % align)
+		return -ENOTSUPP;
+
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (!ret)
+		ipa_dma_dev = dev;
+
+	return ret;
+}
+
+void ipa_dma_exit(void)
+{
+	ipa_dma_dev = NULL;
+}
+
+int ipa_dma_alloc(struct ipa_dma_mem *mem, size_t size, gfp_t gfp)
+{
+	dma_addr_t phys;
+	void *virt;
+
+	virt = dma_zalloc_coherent(ipa_dma_dev, size, &phys, gfp);
+	if (!virt)
+		return -ENOMEM;
+
+	mem->virt = virt;
+	mem->phys = phys;
+	mem->size = size;
+
+	return 0;
+}
+
+void ipa_dma_free(struct ipa_dma_mem *mem)
+{
+	dma_free_coherent(ipa_dma_dev, mem->size, mem->virt, mem->phys);
+	memset(mem, 0, sizeof(*mem));
+}
+
+void *ipa_dma_phys_to_virt(struct ipa_dma_mem *mem, dma_addr_t phys)
+{
+	return mem->virt + (phys - mem->phys);
+}
diff --git a/drivers/net/ipa/ipa_dma.h b/drivers/net/ipa/ipa_dma.h
new file mode 100644
index 000000000000..e211dbd9d4ec
--- /dev/null
+++ b/drivers/net/ipa/ipa_dma.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_DMA_H_
+#define _IPA_DMA_H_
+
+#include <linux/types.h>
+#include <linux/device.h>
+
+/**
+ * struct ipa_dma_mem - IPA allocated DMA memory descriptor
+ * @virt: host virtual base address of allocated DMA memory
+ * @phys: bus physical base address of DMA memory
+ * @size: size (bytes) of DMA memory
+ */
+struct ipa_dma_mem {
+	void *virt;
+	dma_addr_t phys;
+	size_t size;
+};
+
+/**
+ * ipa_dma_init() - Initialize IPA DMA system.
+ * @dev:	IPA device structure
+ * @align:	Hardware required alignment for DMA memory
+ *
+ * Returns:	 0 if successful, or a negative error code.
+ */
+int ipa_dma_init(struct device *dev, u32 align);
+
+/**
+ * ipa_dma_exit() - shut down/clean up IPA DMA system
+ */
+void ipa_dma_exit(void);
+
+/**
+ * ipa_dma_alloc() - allocate a DMA buffer, describe it in mem struct
+ * @mem:	Memory structure to fill with allocation information.
+ * @size:	Size of DMA buffer to allocate.
+ * @gfp:	Allocation mode.
+ */
+int ipa_dma_alloc(struct ipa_dma_mem *mem, size_t size, gfp_t gfp);
+
+/**
+ * ipa_dma_free() - free a previously-allocated DMA buffer
+ * @mem:	Information about DMA allocation to free
+ */
+void ipa_dma_free(struct ipa_dma_mem *mem);
+
+/**
+ * ipa_dma_phys_to_virt() - return the virtual equivalent of a DMA address
+ * @phys:	DMA allocation information
+ * @phys:	Physical address to convert
+ *
+ * Return:	Virtual address corresponding to the given physical address
+ */
+void *ipa_dma_phys_to_virt(struct ipa_dma_mem *mem, dma_addr_t phys);
+
+#endif /* !_IPA_DMA_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 03/12] soc: qcom: ipa: generic software interface
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This patch contains the code supporting the Generic Software
Interface (GSI) used by the IPA.  Although the GSI is an integral
part of the IPA, it provides a well-defined layer between the AP
subsystem (or, for that matter, the modem) and the IPA core.

The GSI code presents an abstract interface through which commands
and data transfers can be queued to be implemented on a channel.  A
hardware independent gsi_xfer_elem structure describes a single
transfer, and an array of these can be queued on a channel.  The
information in the gsi_xfer_elem is converted by the GSI layer into
the specific layout required by the hardware.

A channel has an associated event ring, through which completion of
channel commands can be signaled.  GSI channel commands are completed
in order, and may optionally generate an interrupt on completion.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/gsi.c     | 1685 +++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/gsi.h     |  195 +++++
 drivers/net/ipa/gsi_reg.h |  563 +++++++++++++
 3 files changed, 2443 insertions(+)
 create mode 100644 drivers/net/ipa/gsi.c
 create mode 100644 drivers/net/ipa/gsi.h
 create mode 100644 drivers/net/ipa/gsi_reg.h

diff --git a/drivers/net/ipa/gsi.c b/drivers/net/ipa/gsi.c
new file mode 100644
index 000000000000..348ee1fc1bf5
--- /dev/null
+++ b/drivers/net/ipa/gsi.c
@@ -0,0 +1,1685 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include <linux/bitfield.h>
+#include <linux/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include "gsi.h"
+#include "gsi_reg.h"
+#include "ipa_dma.h"
+#include "ipa_i.h"	/* ipa_err() */
+
+/**
+ * DOC: The Role of GSI in IPA Operation
+ *
+ * The generic software interface (GSI) is an integral component of
+ * the IPA, providing a well-defined layer between the AP subsystem
+ * (or, for that matter, the modem) and the IPA core::
+ *
+ *  ----------   -------------   ---------
+ *  |        |   |G|       |G|   |       |
+ *  |  APSS  |===|S|  IPA  |S|===| Modem |
+ *  |        |   |I|       |I|   |       |
+ *  ----------   -------------   ---------
+ *
+ * In the above diagram, the APSS and Modem represent "execution
+ * environments" (EEs), which are independent operating environments
+ * that use the IPA for data transfer.
+ *
+ * Each EE uses a set of unidirectional GSI "channels," which allow
+ * transfer of data to or from the IPA.  A channel is implemented as a
+ * ring buffer, with a DRAM-resident array of "transfer elements" (TREs)
+ * available to describe transfers to or from other EEs through the IPA.
+ * A transfer element can also contain an immediate command, requesting
+ * the IPA perform actions other than data transfer.
+ *
+ * Each transfer element refers to a block of data--also located DRAM.
+ * After writing one or more TREs to a channel, the writer (either the
+ * IPA or an EE) writes a doorbell register to inform the receiving side
+ * how many elements have been written.  Writing to a doorbell register
+ * triggers an interrupt on the receiver.
+ *
+ * Each channel has a GSI "event ring" associated with it.  An event
+ * ring is implemented very much like a channel ring, but is always
+ * directed from the IPA to an EE.  The IPA notifies an EE (such as
+ * the AP) about channel events by adding an entry to the event ring
+ * associated with the channel; when it writes the event ring's
+ * doorbell register the EE will be interrupted.
+ *
+ * A transfer element has a set of flags.  One flag indicates whether
+ * the completion of the transfer operation generates a channel event.
+ * Another flag allows transfer elements to be chained together,
+ * forming a single logical transaction.  These flags are used to
+ * control whether and when interrupts are generated to signal
+ * completion of a channel transfer.
+ *
+ * Elements in channel and event rings are completed (or consumed)
+ * strictly in order.  Completion of one entry implies the completion
+ * of all preceding entries.  A single completion interrupt can
+ * therefore be used to communicate the completion of many transfers.
+ */
+
+#define GSI_RING_ELEMENT_SIZE	16	/* bytes (channel or event ring) */
+
+#define GSI_CHAN_MAX		14
+#define GSI_EVT_RING_MAX	10
+
+/* Delay period if interrupt moderation is in effect */
+#define IPA_GSI_EVT_RING_INT_MODT	(32 * 1) /* 1ms under 32KHz clock */
+
+#define GSI_CMD_TIMEOUT		msecs_to_jiffies(5 * MSEC_PER_SEC)
+
+#define GSI_MHI_ER_START	10	/* First reserved event number */
+#define GSI_MHI_ER_END		16	/* Last reserved event number */
+
+#define GSI_RESET_WA_MIN_SLEEP	1000	/* microseconds */
+#define GSI_RESET_WA_MAX_SLEEP	2000	/* microseconds */
+
+#define GSI_MAX_PREFETCH	0	/* 0 means 1 segment; 1 means 2 */
+
+#define GSI_ISR_MAX_ITER	50
+
+/* Hardware values from the error log register code field */
+enum gsi_err_code {
+	GSI_INVALID_TRE_ERR			= 0x1,
+	GSI_OUT_OF_BUFFERS_ERR			= 0x2,
+	GSI_OUT_OF_RESOURCES_ERR		= 0x3,
+	GSI_UNSUPPORTED_INTER_EE_OP_ERR		= 0x4,
+	GSI_EVT_RING_EMPTY_ERR			= 0x5,
+	GSI_NON_ALLOCATED_EVT_ACCESS_ERR	= 0x6,
+	GSI_HWO_1_ERR				= 0x8,
+};
+
+/* Hardware values used when programming an event ring context */
+enum gsi_evt_chtype {
+	GSI_EVT_CHTYPE_MHI_EV	= 0x0,
+	GSI_EVT_CHTYPE_XHCI_EV	= 0x1,
+	GSI_EVT_CHTYPE_GPI_EV	= 0x2,
+	GSI_EVT_CHTYPE_XDCI_EV	= 0x3,
+};
+
+/* Hardware values used when programming a channel context */
+enum gsi_channel_protocol {
+	GSI_CHANNEL_PROTOCOL_MHI	= 0x0,
+	GSI_CHANNEL_PROTOCOL_XHCI	= 0x1,
+	GSI_CHANNEL_PROTOCOL_GPI	= 0x2,
+	GSI_CHANNEL_PROTOCOL_XDCI	= 0x3,
+};
+
+/* Hardware values returned in a transfer completion event structure */
+enum gsi_channel_evt {
+	GSI_CHANNEL_EVT_INVALID		= 0x0,
+	GSI_CHANNEL_EVT_SUCCESS		= 0x1,
+	GSI_CHANNEL_EVT_EOT		= 0x2,
+	GSI_CHANNEL_EVT_OVERFLOW	= 0x3,
+	GSI_CHANNEL_EVT_EOB		= 0x4,
+	GSI_CHANNEL_EVT_OOB		= 0x5,
+	GSI_CHANNEL_EVT_DB_MODE		= 0x6,
+	GSI_CHANNEL_EVT_UNDEFINED	= 0x10,
+	GSI_CHANNEL_EVT_RE_ERROR	= 0x11,
+};
+
+/* Hardware values signifying the state of an event ring */
+enum gsi_evt_ring_state {
+	GSI_EVT_RING_STATE_NOT_ALLOCATED	= 0x0,
+	GSI_EVT_RING_STATE_ALLOCATED		= 0x1,
+	GSI_EVT_RING_STATE_ERROR		= 0xf,
+};
+
+/* Hardware values signifying the state of a channel */
+enum gsi_channel_state {
+	GSI_CHANNEL_STATE_NOT_ALLOCATED	= 0x0,
+	GSI_CHANNEL_STATE_ALLOCATED	= 0x1,
+	GSI_CHANNEL_STATE_STARTED	= 0x2,
+	GSI_CHANNEL_STATE_STOPPED	= 0x3,
+	GSI_CHANNEL_STATE_STOP_IN_PROC	= 0x4,
+	GSI_CHANNEL_STATE_ERROR		= 0xf,
+};
+
+struct gsi_ring {
+	spinlock_t slock;		/* protects wp, rp updates */
+	struct ipa_dma_mem mem;
+	u64 wp;
+	u64 rp;
+	u64 wp_local;
+	u64 rp_local;
+	u64 end;			/* physical addr past last element */
+};
+
+struct gsi_channel {
+	bool from_ipa;			/* true: IPA->AP; false: AP->IPA */
+	bool priority;		/* Does hardware give this channel priority? */
+	enum gsi_channel_state state;
+	struct gsi_ring ring;
+	void *notify_data;
+	void **user_data;
+	struct gsi_evt_ring *evt_ring;
+	struct mutex mutex;		/* protects channel_scratch updates */
+	struct completion compl;
+	atomic_t poll_mode;
+	u32 tlv_count;			/* # slots in TLV */
+};
+
+struct gsi_evt_ring {
+	bool moderation;
+	enum gsi_evt_ring_state state;
+	struct gsi_ring ring;
+	struct completion compl;
+	struct gsi_channel *channel;
+};
+
+struct ch_debug_stats {
+	unsigned long ch_allocate;
+	unsigned long ch_start;
+	unsigned long ch_stop;
+	unsigned long ch_reset;
+	unsigned long ch_de_alloc;
+	unsigned long ch_db_stop;
+	unsigned long cmd_completed;
+};
+
+struct gsi {
+	void __iomem *base;
+	struct device *dev;
+	u32 phys;
+	unsigned int irq;
+	bool irq_wake_enabled;
+	spinlock_t slock;	/* protects global register updates */
+	struct mutex mutex;	/* protects 1-at-a-time commands, evt_bmap */
+	atomic_t channel_count;
+	atomic_t evt_ring_count;
+	struct gsi_channel channel[GSI_CHAN_MAX];
+	struct ch_debug_stats ch_dbg[GSI_CHAN_MAX];
+	struct gsi_evt_ring evt_ring[GSI_EVT_RING_MAX];
+	unsigned long evt_bmap;
+	u32 channel_max;
+	u32 evt_ring_max;
+};
+
+/* Hardware values representing a transfer element type */
+enum gsi_re_type {
+	GSI_RE_XFER	= 0x2,
+	GSI_RE_IMMD_CMD	= 0x3,
+	GSI_RE_NOP	= 0x4,
+};
+
+struct gsi_tre {
+	u64 buffer_ptr;
+	u16 buf_len;
+	u16 rsvd1;
+	u8  chain	: 1,
+	    rsvd4	: 7;
+	u8  ieob	: 1,
+	    ieot	: 1,
+	    bei		: 1,
+	    rsvd3	: 5;
+	u8 re_type;
+	u8 rsvd2;
+} __packed;
+
+struct gsi_xfer_compl_evt {
+	u64 xfer_ptr;
+	u16 len;
+	u8 rsvd1;
+	u8 code;  /* see gsi_channel_evt */
+	u16 rsvd;
+	u8 type;
+	u8 chid;
+} __packed;
+
+/* Hardware values from the error log register error type field */
+enum gsi_err_type {
+	GSI_ERR_TYPE_GLOB	= 0x1,
+	GSI_ERR_TYPE_CHAN	= 0x2,
+	GSI_ERR_TYPE_EVT	= 0x3,
+};
+
+struct gsi_log_err {
+	u8  arg3	: 4,
+	    arg2	: 4;
+	u8  arg1	: 4,
+	    code	: 4;
+	u8  rsvd	: 3,
+	    virt_idx	: 5;
+	u8  err_type	: 4,
+	    ee		: 4;
+} __packed;
+
+/* Hardware values repreasenting a channel immediate command opcode */
+enum gsi_ch_cmd_opcode {
+	GSI_CH_ALLOCATE	= 0x0,
+	GSI_CH_START	= 0x1,
+	GSI_CH_STOP	= 0x2,
+	GSI_CH_RESET	= 0x9,
+	GSI_CH_DE_ALLOC	= 0xa,
+	GSI_CH_DB_STOP	= 0xb,
+};
+
+/* Hardware values repreasenting an event ring immediate command opcode */
+enum gsi_evt_ch_cmd_opcode {
+	GSI_EVT_ALLOCATE	= 0x0,
+	GSI_EVT_RESET		= 0x9,
+	GSI_EVT_DE_ALLOC	= 0xa,
+};
+
+/** gsi_gpi_channel_scratch - GPI protocol SW config area of channel scratch
+ *
+ * @max_outstanding_tre: Used for the prefetch management sequence by the
+ *			 sequencer. Defines the maximum number of allowed
+ *			 outstanding TREs in IPA/GSI (in Bytes). RE engine
+ *			 prefetch will be limited by this configuration. It
+ *			 is suggested to configure this value to IPA_IF
+ *			 channel TLV queue size times element size. To disable
+ *			 the feature in doorbell mode (DB Mode=1). Maximum
+ *			 outstanding TREs should be set to 64KB
+ *			 (or any value larger or equal to ring length . RLEN)
+ * @outstanding_threshold: Used for the prefetch management sequence by the
+ *			 sequencer. Defines the threshold (in Bytes) as to when
+ *			 to update the channel doorbell. Should be smaller than
+ *			 Maximum outstanding TREs. value. It is suggested to
+ *			 configure this value to 2 * element size.
+ */
+struct gsi_gpi_channel_scratch {
+	u64 rsvd1;
+	u16 rsvd2;
+	u16 max_outstanding_tre;
+	u16 rsvd3;
+	u16 outstanding_threshold;
+} __packed;
+
+/** gsi_channel_scratch - channel scratch SW config area */
+union gsi_channel_scratch {
+	struct gsi_gpi_channel_scratch gpi;
+	struct {
+		u32 word1;
+		u32 word2;
+		u32 word3;
+		u32 word4;
+	} data;
+} __packed;
+
+/* Read a value from the given offset into the I/O space defined in
+ * the GSI context.
+ */
+static u32 gsi_readl(struct gsi *gsi, u32 offset)
+{
+	return readl(gsi->base + offset);
+}
+
+/* Write the provided value to the given offset into the I/O space
+ * defined in the GSI context.
+ */
+static void gsi_writel(struct gsi *gsi, u32 v, u32 offset)
+{
+	writel(v, gsi->base + offset);
+}
+
+static void
+_gsi_irq_control_event(struct gsi *gsi, u32 evt_ring_id, bool enable)
+{
+	u32 mask = BIT(evt_ring_id);
+	u32 val;
+
+	val = gsi_readl(gsi, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+	if (enable)
+		val |= mask;
+	else
+		val &= ~mask;
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+}
+
+static void gsi_irq_disable_event(struct gsi *gsi, u32 evt_ring_id)
+{
+	_gsi_irq_control_event(gsi, evt_ring_id, false);
+}
+
+static void gsi_irq_enable_event(struct gsi *gsi, u32 evt_ring_id)
+{
+	_gsi_irq_control_event(gsi, evt_ring_id, true);
+}
+
+static void _gsi_irq_control_all(struct gsi *gsi, bool enable)
+{
+	u32 val = enable ? ~0 : 0;
+
+	/* Inter EE commands / interrupt are no supported. */
+	gsi_writel(gsi, val, GSI_CNTXT_TYPE_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_CH_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_GLOB_IRQ_EN_OFFS);
+	/* Never enable GSI_BREAK_POINT */
+	val &= ~FIELD_PREP(EN_BREAK_POINT_FMASK, 1);
+	gsi_writel(gsi, val, GSI_CNTXT_GSI_IRQ_EN_OFFS);
+}
+
+static void gsi_irq_disable_all(struct gsi *gsi)
+{
+	_gsi_irq_control_all(gsi, false);
+}
+
+static void gsi_irq_enable_all(struct gsi *gsi)
+{
+	_gsi_irq_control_all(gsi, true);
+}
+
+static u32 gsi_channel_id(struct gsi *gsi, struct gsi_channel *channel)
+{
+	return (u32)(channel - &gsi->channel[0]);
+}
+
+static u32 gsi_evt_ring_id(struct gsi *gsi, struct gsi_evt_ring *evt_ring)
+{
+	return (u32)(evt_ring - &gsi->evt_ring[0]);
+}
+
+static enum gsi_channel_state gsi_channel_state(struct gsi *gsi, u32 channel_id)
+{
+	u32 val = gsi_readl(gsi, GSI_CH_C_CNTXT_0_OFFS(channel_id));
+
+	return (enum gsi_channel_state)FIELD_GET(CHSTATE_FMASK, val);
+}
+
+static enum gsi_evt_ring_state
+gsi_evt_ring_state(struct gsi *gsi, u32 evt_ring_id)
+{
+	u32 val = gsi_readl(gsi, GSI_EV_CH_E_CNTXT_0_OFFS(evt_ring_id));
+
+	return (enum gsi_evt_ring_state)FIELD_GET(EV_CHSTATE_FMASK, val);
+}
+
+static void gsi_isr_chan_ctrl(struct gsi *gsi)
+{
+	u32 channel_mask;
+
+	channel_mask = gsi_readl(gsi, GSI_CNTXT_SRC_CH_IRQ_OFFS);
+	gsi_writel(gsi, channel_mask, GSI_CNTXT_SRC_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(channel_mask & ~GENMASK(gsi->channel_max - 1, 0)));
+
+	while (channel_mask) {
+		struct gsi_channel *channel;
+		int i = __ffs(channel_mask);
+
+		channel = &gsi->channel[i];
+		channel->state = gsi_channel_state(gsi, i);
+
+		complete(&channel->compl);
+
+		channel_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_evt_ctrl(struct gsi *gsi)
+{
+	u32 evt_mask;
+
+	evt_mask = gsi_readl(gsi, GSI_CNTXT_SRC_EV_CH_IRQ_OFFS);
+	gsi_writel(gsi, evt_mask, GSI_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(evt_mask & ~GENMASK(gsi->evt_ring_max - 1, 0)));
+
+	while (evt_mask) {
+		struct gsi_evt_ring *evt_ring;
+		int i = __ffs(evt_mask);
+
+		evt_ring = &gsi->evt_ring[i];
+		evt_ring->state = gsi_evt_ring_state(gsi, i);
+
+		complete(&evt_ring->compl);
+
+		evt_mask ^= BIT(i);
+	}
+}
+
+static void
+gsi_isr_glob_chan_err(struct gsi *gsi, u32 err_ee, u32 channel_id, u32 code)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+
+	if (err_ee != IPA_EE_AP)
+		ipa_bug_on(code != GSI_UNSUPPORTED_INTER_EE_OP_ERR);
+
+	if (WARN_ON(channel_id >= gsi->channel_max)) {
+		ipa_err("unexpected channel_id %u\n", channel_id);
+		return;
+	}
+
+	switch (code) {
+	case GSI_INVALID_TRE_ERR:
+		ipa_err("got INVALID_TRE_ERR\n");
+		channel->state = gsi_channel_state(gsi, channel_id);
+		ipa_bug_on(channel->state != GSI_CHANNEL_STATE_ERROR);
+		break;
+	case GSI_OUT_OF_BUFFERS_ERR:
+		ipa_err("got OUT_OF_BUFFERS_ERR\n");
+		break;
+	case GSI_OUT_OF_RESOURCES_ERR:
+		ipa_err("got OUT_OF_RESOURCES_ERR\n");
+		complete(&channel->compl);
+		break;
+	case GSI_UNSUPPORTED_INTER_EE_OP_ERR:
+		ipa_err("got UNSUPPORTED_INTER_EE_OP_ERR\n");
+		break;
+	case GSI_NON_ALLOCATED_EVT_ACCESS_ERR:
+		ipa_err("got NON_ALLOCATED_EVT_ACCESS_ERR\n");
+		break;
+	case GSI_HWO_1_ERR:
+		ipa_err("got HWO_1_ERR\n");
+		break;
+	default:
+		ipa_err("unexpected channel error code %u\n", code);
+		ipa_bug();
+	}
+}
+
+static void
+gsi_isr_glob_evt_err(struct gsi *gsi, u32 err_ee, u32 evt_ring_id, u32 code)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+
+	if (err_ee != IPA_EE_AP)
+		ipa_bug_on(code != GSI_UNSUPPORTED_INTER_EE_OP_ERR);
+
+	if (WARN_ON(evt_ring_id >= gsi->evt_ring_max)) {
+		ipa_err("unexpected evt_ring_id %u\n", evt_ring_id);
+		return;
+	}
+
+	switch (code) {
+	case GSI_OUT_OF_BUFFERS_ERR:
+		ipa_err("got OUT_OF_BUFFERS_ERR\n");
+		break;
+	case GSI_OUT_OF_RESOURCES_ERR:
+		ipa_err("got OUT_OF_RESOURCES_ERR\n");
+		complete(&evt_ring->compl);
+		break;
+	case GSI_UNSUPPORTED_INTER_EE_OP_ERR:
+		ipa_err("got UNSUPPORTED_INTER_EE_OP_ERR\n");
+		break;
+	case GSI_EVT_RING_EMPTY_ERR:
+		ipa_err("got EVT_RING_EMPTY_ERR\n");
+		break;
+	default:
+		ipa_err("unexpected event error code %u\n", code);
+		ipa_bug();
+	}
+}
+
+static void gsi_isr_glob_err(struct gsi *gsi, u32 err)
+{
+	struct gsi_log_err *log = (struct gsi_log_err *)&err;
+
+	ipa_err("log err_type %u ee %u idx %u\n", log->err_type, log->ee,
+		log->virt_idx);
+	ipa_err("log code 0x%1x arg1 0x%1x arg2 0x%1x arg3 0x%1x\n", log->code,
+		log->arg1, log->arg2, log->arg3);
+
+	ipa_bug_on(log->err_type == GSI_ERR_TYPE_GLOB);
+
+	switch (log->err_type) {
+	case GSI_ERR_TYPE_CHAN:
+		gsi_isr_glob_chan_err(gsi, log->ee, log->virt_idx, log->code);
+		break;
+	case GSI_ERR_TYPE_EVT:
+		gsi_isr_glob_evt_err(gsi, log->ee, log->virt_idx, log->code);
+		break;
+	default:
+		WARN_ON(1);
+	}
+}
+
+static void gsi_isr_glob_ee(struct gsi *gsi)
+{
+	u32 val;
+
+	val = gsi_readl(gsi, GSI_CNTXT_GLOB_IRQ_STTS_OFFS);
+
+	if (val & ERROR_INT_FMASK) {
+		u32 err = gsi_readl(gsi, GSI_ERROR_LOG_OFFS);
+
+		gsi_writel(gsi, 0, GSI_ERROR_LOG_OFFS);
+		gsi_writel(gsi, ~0, GSI_ERROR_LOG_CLR_OFFS);
+
+		gsi_isr_glob_err(gsi, err);
+	}
+
+	if (val & EN_GP_INT1_FMASK)
+		ipa_err("unexpected GP INT1 received\n");
+
+	ipa_bug_on(val & EN_GP_INT2_FMASK);
+	ipa_bug_on(val & EN_GP_INT3_FMASK);
+
+	gsi_writel(gsi, val, GSI_CNTXT_GLOB_IRQ_CLR_OFFS);
+}
+
+static void ring_wp_local_inc(struct gsi_ring *ring)
+{
+	ring->wp_local += GSI_RING_ELEMENT_SIZE;
+	if (ring->wp_local == ring->end)
+		ring->wp_local = ring->mem.phys;
+}
+
+static void ring_rp_local_inc(struct gsi_ring *ring)
+{
+	ring->rp_local += GSI_RING_ELEMENT_SIZE;
+	if (ring->rp_local == ring->end)
+		ring->rp_local = ring->mem.phys;
+}
+
+static u16 ring_rp_local_index(struct gsi_ring *ring)
+{
+	return (u16)(ring->rp_local - ring->mem.phys) / GSI_RING_ELEMENT_SIZE;
+}
+
+static u16 ring_wp_local_index(struct gsi_ring *ring)
+{
+	return (u16)(ring->wp_local - ring->mem.phys) / GSI_RING_ELEMENT_SIZE;
+}
+
+static void channel_xfer_cb(struct gsi_channel *channel, u16 count)
+{
+	void *xfer_data;
+
+	if (!channel->from_ipa) {
+		u16 ring_rp_local = ring_rp_local_index(&channel->ring);
+
+		xfer_data = channel->user_data[ring_rp_local];;
+		ipa_gsi_irq_tx_notify_cb(xfer_data);
+	} else {
+		ipa_gsi_irq_rx_notify_cb(channel->notify_data, count);
+	}
+}
+
+static u16 gsi_channel_process(struct gsi *gsi, struct gsi_xfer_compl_evt *evt,
+			       bool callback)
+{
+	struct gsi_channel *channel;
+	u32 channel_id = (u32)evt->chid;
+
+	ipa_assert(channel_id < gsi->channel_max);
+
+	/* Event tells us the last completed channel ring element */
+	channel = &gsi->channel[channel_id];
+	channel->ring.rp_local = evt->xfer_ptr;
+
+	if (callback) {
+		if (evt->code == GSI_CHANNEL_EVT_EOT)
+			channel_xfer_cb(channel, evt->len);
+		else
+			ipa_err("ch %u unexpected %sX event id %hhu\n",
+				channel_id, channel->from_ipa ? "R" : "T",
+				evt->code);
+	}
+
+	/* Record that we've processed this channel ring element. */
+	ring_rp_local_inc(&channel->ring);
+	channel->ring.rp = channel->ring.rp_local;
+
+	return evt->len;
+}
+
+static void
+gsi_evt_ring_doorbell(struct gsi *gsi, struct gsi_evt_ring *evt_ring)
+{
+	u32 evt_ring_id = gsi_evt_ring_id(gsi, evt_ring);
+	u32 val;
+
+	/* The doorbell 0 and 1 registers store the low-order and
+	 * high-order 32 bits of the event ring doorbell register,
+	 * respectively.  LSB (doorbell 0) must be written last.
+	 */
+	val = evt_ring->ring.wp_local >> 32;
+	gsi_writel(gsi, val, GSI_EV_CH_E_DOORBELL_1_OFFS(evt_ring_id));
+
+	val = evt_ring->ring.wp_local & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_EV_CH_E_DOORBELL_0_OFFS(evt_ring_id));
+}
+
+static void gsi_channel_doorbell(struct gsi *gsi, struct gsi_channel *channel)
+{
+	u32 channel_id = gsi_channel_id(gsi, channel);
+	u32 val;
+
+	/* allocate new events for this channel first
+	 * before submitting the new TREs.
+	 * for TO_GSI channels the event ring doorbell is rang as part of
+	 * interrupt handling.
+	 */
+	if (channel->from_ipa)
+		gsi_evt_ring_doorbell(gsi, channel->evt_ring);
+	channel->ring.wp = channel->ring.wp_local;
+
+	/* The doorbell 0 and 1 registers store the low-order and
+	 * high-order 32 bits of the channel ring doorbell register,
+	 * respectively.  LSB (doorbell 0) must be written last.
+	 */
+	val = channel->ring.wp_local >> 32;
+	gsi_writel(gsi, val, GSI_CH_C_DOORBELL_1_OFFS(channel_id));
+	val = channel->ring.wp_local & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_CH_C_DOORBELL_0_OFFS(channel_id));
+}
+
+static void gsi_event_handle(struct gsi *gsi, u32 evt_ring_id)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+	unsigned long flags;
+	bool check_again;
+
+	spin_lock_irqsave(&evt_ring->ring.slock, flags);
+
+	do {
+		u32 val = gsi_readl(gsi, GSI_EV_CH_E_CNTXT_4_OFFS(evt_ring_id));
+
+		evt_ring->ring.rp = evt_ring->ring.rp & GENMASK_ULL(63, 32);
+		evt_ring->ring.rp |= val;
+
+		check_again = false;
+		while (evt_ring->ring.rp_local != evt_ring->ring.rp) {
+			struct gsi_xfer_compl_evt *evt;
+
+			if (atomic_read(&evt_ring->channel->poll_mode)) {
+				check_again = false;
+				break;
+			}
+			check_again = true;
+
+			evt = ipa_dma_phys_to_virt(&evt_ring->ring.mem,
+						   evt_ring->ring.rp_local);
+			(void)gsi_channel_process(gsi, evt, true);
+
+			ring_rp_local_inc(&evt_ring->ring);
+			ring_wp_local_inc(&evt_ring->ring); /* recycle */
+		}
+
+		gsi_evt_ring_doorbell(gsi, evt_ring);
+	} while (check_again);
+
+	spin_unlock_irqrestore(&evt_ring->ring.slock, flags);
+}
+
+static void gsi_isr_ioeb(struct gsi *gsi)
+{
+	u32 evt_mask;
+
+	evt_mask = gsi_readl(gsi, GSI_CNTXT_SRC_IEOB_IRQ_OFFS);
+	evt_mask &= gsi_readl(gsi, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+	gsi_writel(gsi, evt_mask, GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFS);
+
+	ipa_assert(!(evt_mask & ~GENMASK(gsi->evt_ring_max - 1, 0)));
+
+	while (evt_mask) {
+		u32 i = (u32)__ffs(evt_mask);
+
+		gsi_event_handle(gsi, i);
+
+		evt_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_inter_ee_chan_ctrl(struct gsi *gsi)
+{
+	u32 channel_mask;
+
+	channel_mask = gsi_readl(gsi, GSI_INTER_EE_SRC_CH_IRQ_OFFS);
+	gsi_writel(gsi, channel_mask, GSI_INTER_EE_SRC_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(channel_mask & ~GENMASK(gsi->channel_max - 1, 0)));
+
+	while (channel_mask) {
+		int i = __ffs(channel_mask);
+
+		/* not currently expected */
+		ipa_err("ch %d was inter-EE changed\n", i);
+		channel_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_inter_ee_evt_ctrl(struct gsi *gsi)
+{
+	u32 evt_mask;
+
+	evt_mask = gsi_readl(gsi, GSI_INTER_EE_SRC_EV_CH_IRQ_OFFS);
+	gsi_writel(gsi, evt_mask, GSI_INTER_EE_SRC_EV_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(evt_mask & ~GENMASK(gsi->evt_ring_max - 1, 0)));
+
+	while (evt_mask) {
+		u32 i = (u32)__ffs(evt_mask);
+
+		/* not currently expected */
+		ipa_err("evt %d was inter-EE changed\n", i);
+		evt_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_general(struct gsi *gsi)
+{
+	u32 val;
+
+	val = gsi_readl(gsi, GSI_CNTXT_GSI_IRQ_STTS_OFFS);
+
+	ipa_bug_on(val & CLR_MCS_STACK_OVRFLOW_FMASK);
+	ipa_bug_on(val & CLR_CMD_FIFO_OVRFLOW_FMASK);
+	ipa_bug_on(val & CLR_BUS_ERROR_FMASK);
+
+	if (val & CLR_BREAK_POINT_FMASK)
+		ipa_err("got breakpoint\n");
+
+	gsi_writel(gsi, val, GSI_CNTXT_GSI_IRQ_CLR_OFFS);
+}
+
+/* Returns a bitmask of pending GSI interrupts */
+static u32 gsi_isr_type(struct gsi *gsi)
+{
+	return gsi_readl(gsi, GSI_CNTXT_TYPE_IRQ_OFFS);
+}
+
+static irqreturn_t gsi_isr(int irq, void *dev_id)
+{
+	struct gsi *gsi = dev_id;
+	u32 type;
+	u32 cnt;
+
+	cnt = 0;
+	while ((type = gsi_isr_type(gsi))) {
+		do {
+			u32 single = BIT(__ffs(type));
+
+			switch (single) {
+			case CH_CTRL_FMASK:
+				gsi_isr_chan_ctrl(gsi);
+				break;
+			case EV_CTRL_FMASK:
+				gsi_isr_evt_ctrl(gsi);
+				break;
+			case GLOB_EE_FMASK:
+				gsi_isr_glob_ee(gsi);
+				break;
+			case IEOB_FMASK:
+				gsi_isr_ioeb(gsi);
+				break;
+			case INTER_EE_CH_CTRL_FMASK:
+				gsi_isr_inter_ee_chan_ctrl(gsi);
+				break;
+			case INTER_EE_EV_CTRL_FMASK:
+				gsi_isr_inter_ee_evt_ctrl(gsi);
+				break;
+			case GENERAL_FMASK:
+				gsi_isr_general(gsi);
+				break;
+			default:
+				WARN(true, "%s: unrecognized type 0x%08x\n",
+				     __func__, single);
+				break;
+			}
+			type ^= single;
+		} while (type);
+
+		ipa_bug_on(++cnt > GSI_ISR_MAX_ITER);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static u32 gsi_channel_max(struct gsi *gsi)
+{
+	u32 val = gsi_readl(gsi, GSI_GSI_HW_PARAM_2_OFFS);
+
+	return FIELD_GET(NUM_CH_PER_EE_FMASK, val);
+}
+
+static u32 gsi_evt_ring_max(struct gsi *gsi)
+{
+	u32 val = gsi_readl(gsi, GSI_GSI_HW_PARAM_2_OFFS);
+
+	return FIELD_GET(NUM_EV_PER_EE_FMASK, val);
+}
+
+/* Zero bits in an event bitmap represent event numbers available
+ * for allocation.  Initialize the map so all events supported by
+ * the hardware are available; then preclude any reserved events
+ * from allocation.
+ */
+static u32 gsi_evt_bmap_init(u32 evt_ring_max)
+{
+	u32 evt_bmap = GENMASK(BITS_PER_LONG - 1, evt_ring_max);
+
+	return evt_bmap | GENMASK(GSI_MHI_ER_END, GSI_MHI_ER_START);
+}
+
+int gsi_device_init(struct gsi *gsi)
+{
+	u32 evt_ring_max;
+	u32 channel_max;
+	u32 val;
+	int ret;
+
+	val = gsi_readl(gsi, GSI_GSI_STATUS_OFFS);
+	if (!(val & ENABLED_FMASK)) {
+		ipa_err("manager EE has not enabled GSI, GSI un-usable\n");
+		return -EIO;
+	}
+
+	channel_max = gsi_channel_max(gsi);
+	ipa_debug("channel_max %u\n", channel_max);
+	ipa_assert(channel_max <= GSI_CHAN_MAX);
+
+	evt_ring_max = gsi_evt_ring_max(gsi);
+	ipa_debug("evt_ring_max %u\n", evt_ring_max);
+	ipa_assert(evt_ring_max <= GSI_EVT_RING_MAX);
+
+	ret = request_irq(gsi->irq, gsi_isr, IRQF_TRIGGER_HIGH, "gsi", gsi);
+	if (ret) {
+		ipa_err("failed to register isr for %u\n", gsi->irq);
+		return -EIO;
+	}
+
+	ret = enable_irq_wake(gsi->irq);
+	if (ret)
+		ipa_err("error %d enabling gsi wake irq\n", ret);
+	gsi->irq_wake_enabled = !ret;
+	gsi->channel_max = channel_max;
+	gsi->evt_ring_max = evt_ring_max;
+	gsi->evt_bmap = gsi_evt_bmap_init(evt_ring_max);
+
+	/* Enable all IPA interrupts */
+	gsi_irq_enable_all(gsi);
+
+	/* Writing 1 indicates IRQ interrupts; 0 would be MSI */
+	gsi_writel(gsi, 1, GSI_CNTXT_INTSET_OFFS);
+
+	/* Initialize the error log */
+	gsi_writel(gsi, 0, GSI_ERROR_LOG_OFFS);
+
+	return 0;
+}
+
+void gsi_device_exit(struct gsi *gsi)
+{
+	ipa_assert(!atomic_read(&gsi->channel_count));
+	ipa_assert(!atomic_read(&gsi->evt_ring_count));
+
+	/* Don't bother clearing the error log again (ERROR_LOG) or
+	 * setting the interrupt type again (INTSET).
+	 */
+	gsi_irq_disable_all(gsi);
+
+	/* Clean up everything else set up by gsi_device_init() */
+	gsi->evt_bmap = 0;
+	gsi->evt_ring_max = 0;
+	gsi->channel_max = 0;
+	if (gsi->irq_wake_enabled) {
+		(void)disable_irq_wake(gsi->irq);
+		gsi->irq_wake_enabled = false;
+	}
+	free_irq(gsi->irq, gsi);
+	gsi->irq = 0;
+}
+
+static void gsi_evt_ring_program(struct gsi *gsi, u32 evt_ring_id)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+	u32 int_modt;
+	u32 int_modc;
+	u64 phys;
+	u32 val;
+
+	phys = evt_ring->ring.mem.phys;
+	int_modt = evt_ring->moderation ? IPA_GSI_EVT_RING_INT_MODT : 0;
+	int_modc = 1;	/* moderation always comes from channel*/
+
+	val = FIELD_PREP(EV_CHTYPE_FMASK, GSI_EVT_CHTYPE_GPI_EV);
+	val |= FIELD_PREP(EV_INTYPE_FMASK, 1);
+	val |= FIELD_PREP(EV_ELEMENT_SIZE_FMASK, GSI_RING_ELEMENT_SIZE);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_0_OFFS(evt_ring_id));
+
+	val = FIELD_PREP(EV_R_LENGTH_FMASK, (u32)evt_ring->ring.mem.size);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_1_OFFS(evt_ring_id));
+
+	/* The context 2 and 3 registers store the low-order and
+	 * high-order 32 bits of the address of the event ring,
+	 * respectively.
+	 */
+	val = phys & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_2_OFFS(evt_ring_id));
+
+	val = phys >> 32;
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_3_OFFS(evt_ring_id));
+
+	val = FIELD_PREP(MODT_FMASK, int_modt);
+	val |= FIELD_PREP(MODC_FMASK, int_modc);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_8_OFFS(evt_ring_id));
+
+	/* No MSI write data, and MSI address high and low address is 0 */
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_9_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_10_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_11_OFFS(evt_ring_id));
+
+	/* We don't need to get event read pointer updates */
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_12_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_13_OFFS(evt_ring_id));
+}
+
+static void gsi_ring_init(struct gsi_ring *ring)
+{
+	ring->wp_local = ring->wp = ring->mem.phys;
+	ring->rp_local = ring->rp = ring->mem.phys;
+}
+
+static int gsi_ring_alloc(struct gsi_ring *ring, u32 count)
+{
+	size_t size = roundup_pow_of_two(count * GSI_RING_ELEMENT_SIZE);
+
+	/* Hardware requires a power-of-2 ring size (and alignment) */
+	if (ipa_dma_alloc(&ring->mem, size, GFP_KERNEL))
+		return -ENOMEM;
+	ipa_assert(!(ring->mem.phys % size));
+
+	ring->end = ring->mem.phys + size;
+	spin_lock_init(&ring->slock);
+
+	return 0;
+}
+
+static void gsi_ring_free(struct gsi_ring *ring)
+{
+	ipa_dma_free(&ring->mem);
+	memset(ring, 0, sizeof(*ring));
+}
+
+static void gsi_evt_ring_prime(struct gsi *gsi, struct gsi_evt_ring *evt_ring)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&evt_ring->ring.slock, flags);
+	memset(evt_ring->ring.mem.virt, 0, evt_ring->ring.mem.size);
+	evt_ring->ring.wp_local = evt_ring->ring.end - GSI_RING_ELEMENT_SIZE;
+	gsi_evt_ring_doorbell(gsi, evt_ring);
+	spin_unlock_irqrestore(&evt_ring->ring.slock, flags);
+}
+
+/* Issue a GSI command by writing a value to a register, then wait
+ * for completion to be signaled.  Returns true if successful or
+ * false if a timeout occurred.  Note that the register offset is
+ * first, value to write is second (reverse of writel() order).
+ */
+static bool command(struct gsi *gsi, u32 reg, u32 val, struct completion *compl)
+{
+	bool ret;
+
+	gsi_writel(gsi, val, reg);
+	ret = !!wait_for_completion_timeout(compl, GSI_CMD_TIMEOUT);
+	if (!ret)
+		ipa_err("command timeout\n");
+
+	return ret;
+}
+
+/* Issue an event ring command and wait for it to complete */
+static bool evt_ring_command(struct gsi *gsi, u32 evt_ring_id,
+			     enum gsi_evt_ch_cmd_opcode op)
+{
+	struct completion *compl = &gsi->evt_ring[evt_ring_id].compl;
+	u32 val;
+
+	reinit_completion(compl);
+
+	val = FIELD_PREP(EV_CHID_FMASK, evt_ring_id);
+	val |= FIELD_PREP(EV_OPCODE_FMASK, (u32)op);
+
+	return command(gsi, GSI_EV_CH_CMD_OFFS, val, compl);
+}
+
+/* Issue a channel command and wait for it to complete */
+static bool
+channel_command(struct gsi *gsi, u32 channel_id, enum gsi_ch_cmd_opcode op)
+{
+	struct completion *compl = &gsi->channel[channel_id].compl;
+	u32 val;
+
+	reinit_completion(compl);
+
+	val = FIELD_PREP(CH_CHID_FMASK, channel_id);
+	val |= FIELD_PREP(CH_OPCODE_FMASK, (u32)op);
+
+	return command(gsi, GSI_CH_CMD_OFFS, val, compl);
+}
+
+/* Note: only GPI interfaces, IRQ interrupts are currently supported */
+static int gsi_evt_ring_alloc(struct gsi *gsi, u32 ring_count, bool moderation)
+{
+	struct gsi_evt_ring *evt_ring;
+	unsigned long flags;
+	u32 evt_ring_id;
+	u32 val;
+	int ret;
+
+	/* Get the mutex to allocate from the bitmap and issue a command */
+	mutex_lock(&gsi->mutex);
+
+	/* Start by allocating the event id to use */
+	ipa_assert(gsi->evt_bmap != ~0UL);
+	evt_ring_id = (u32)ffz(gsi->evt_bmap);
+	gsi->evt_bmap |= BIT(evt_ring_id);
+
+	evt_ring = &gsi->evt_ring[evt_ring_id];
+
+	ret = gsi_ring_alloc(&evt_ring->ring, ring_count);
+	if (ret)
+		goto err_free_bmap;
+
+	init_completion(&evt_ring->compl);
+
+	if (!evt_ring_command(gsi, evt_ring_id, GSI_EVT_ALLOCATE)) {
+		ret = -ETIMEDOUT;
+		goto err_free_ring;
+	}
+
+	if (evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED) {
+		ipa_err("evt_ring_id %u allocation failed state %u\n",
+			evt_ring_id, evt_ring->state);
+		ret = -ENOMEM;
+		goto err_free_ring;
+	}
+	atomic_inc(&gsi->evt_ring_count);
+
+	evt_ring->moderation = moderation;
+
+	gsi_evt_ring_program(gsi, evt_ring_id);
+	gsi_ring_init(&evt_ring->ring);
+	gsi_evt_ring_prime(gsi, evt_ring);
+
+	mutex_unlock(&gsi->mutex);
+
+	spin_lock_irqsave(&gsi->slock, flags);
+
+	/* Enable the event interrupt (clear it first in case pending) */
+	val = BIT(evt_ring_id);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFS);
+	gsi_irq_enable_event(gsi, evt_ring_id);
+
+	spin_unlock_irqrestore(&gsi->slock, flags);
+
+	return evt_ring_id;
+
+err_free_ring:
+	gsi_ring_free(&evt_ring->ring);
+	memset(evt_ring, 0, sizeof(*evt_ring));
+err_free_bmap:
+	ipa_assert(gsi->evt_bmap & BIT(evt_ring_id));
+	gsi->evt_bmap &= ~BIT(evt_ring_id);
+
+	mutex_unlock(&gsi->mutex);
+
+	return ret;
+}
+
+static void gsi_evt_ring_scratch_zero(struct gsi *gsi, u32 evt_ring_id)
+{
+	gsi_writel(gsi, 0, GSI_EV_CH_E_SCRATCH_0_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_SCRATCH_1_OFFS(evt_ring_id));
+}
+
+static void gsi_evt_ring_dealloc(struct gsi *gsi, u32 evt_ring_id)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+	bool completed;
+
+	ipa_bug_on(evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED);
+
+	mutex_lock(&gsi->mutex);
+
+	completed = evt_ring_command(gsi, evt_ring_id, GSI_EVT_RESET);
+	ipa_bug_on(!completed);
+	ipa_bug_on(evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED);
+
+	gsi_evt_ring_program(gsi, evt_ring_id);
+	gsi_ring_init(&evt_ring->ring);
+	gsi_evt_ring_scratch_zero(gsi, evt_ring_id);
+	gsi_evt_ring_prime(gsi, evt_ring);
+
+	completed = evt_ring_command(gsi, evt_ring_id, GSI_EVT_DE_ALLOC);
+	ipa_bug_on(!completed);
+
+	ipa_bug_on(evt_ring->state != GSI_EVT_RING_STATE_NOT_ALLOCATED);
+
+	ipa_assert(gsi->evt_bmap & BIT(evt_ring_id));
+	gsi->evt_bmap &= ~BIT(evt_ring_id);
+
+	mutex_unlock(&gsi->mutex);
+
+	evt_ring->moderation = false;
+	gsi_ring_free(&evt_ring->ring);
+	memset(evt_ring, 0, sizeof(*evt_ring));
+
+	atomic_dec(&gsi->evt_ring_count);
+}
+
+static void gsi_channel_program(struct gsi *gsi, u32 channel_id,
+				u32 evt_ring_id, bool doorbell_enable)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 low_weight;
+	u32 val;
+
+	val = FIELD_PREP(CHTYPE_PROTOCOL_FMASK, GSI_CHANNEL_PROTOCOL_GPI);
+	val |= FIELD_PREP(CHTYPE_DIR_FMASK, channel->from_ipa ? 0 : 1);
+	val |= FIELD_PREP(ERINDEX_FMASK, evt_ring_id);
+	val |= FIELD_PREP(ELEMENT_SIZE_FMASK, GSI_RING_ELEMENT_SIZE);
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_0_OFFS(channel_id));
+
+	val = FIELD_PREP(R_LENGTH_FMASK, channel->ring.mem.size);
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_1_OFFS(channel_id));
+
+	/* The context 2 and 3 registers store the low-order and
+	 * high-order 32 bits of the address of the channel ring,
+	 * respectively.
+	 */
+	val = channel->ring.mem.phys & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_2_OFFS(channel_id));
+
+	val = channel->ring.mem.phys >> 32;
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_3_OFFS(channel_id));
+
+	low_weight = channel->priority ? FIELD_MAX(WRR_WEIGHT_FMASK) : 0;
+	val = FIELD_PREP(WRR_WEIGHT_FMASK, low_weight);
+	val |= FIELD_PREP(MAX_PREFETCH_FMASK, GSI_MAX_PREFETCH);
+	val |= FIELD_PREP(USE_DB_ENG_FMASK, doorbell_enable ? 1 : 0);
+	gsi_writel(gsi, val, GSI_CH_C_QOS_OFFS(channel_id));
+}
+
+int gsi_channel_alloc(struct gsi *gsi, u32 channel_id, u32 channel_count,
+		      bool from_ipa, bool priority, u32 evt_ring_mult,
+		      bool moderation, void *notify_data)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_count;
+	u32 evt_ring_id;
+	void **user_data;
+	int ret;
+
+	evt_ring_count = channel_count * evt_ring_mult;
+	ret = gsi_evt_ring_alloc(gsi, evt_ring_count, moderation);
+	if (ret < 0)
+		return ret;
+	evt_ring_id = (u32)ret;
+
+	ret = gsi_ring_alloc(&channel->ring, channel_count);
+	if (ret)
+		goto err_evt_ring_free;
+
+	user_data = kcalloc(channel_count, sizeof(void *), GFP_KERNEL);
+	if (!user_data) {
+		ret = -ENOMEM;
+		goto err_ring_free;
+	}
+
+	mutex_init(&channel->mutex);
+	init_completion(&channel->compl);
+	atomic_set(&channel->poll_mode, 0);	/* Initially in callback mode */
+	channel->from_ipa = from_ipa;
+	channel->notify_data = notify_data;
+
+	mutex_lock(&gsi->mutex);
+
+	if (!channel_command(gsi, channel_id, GSI_CH_ALLOCATE)) {
+		ret = -ETIMEDOUT;
+		goto err_mutex_unlock;
+	}
+	if (channel->state != GSI_CHANNEL_STATE_ALLOCATED) {
+		ret = -EIO;
+		goto err_mutex_unlock;
+	}
+
+	gsi->ch_dbg[channel_id].ch_allocate++;
+
+	mutex_unlock(&gsi->mutex);
+
+	channel->evt_ring = &gsi->evt_ring[evt_ring_id];
+	channel->evt_ring->channel = channel;
+	channel->priority = priority;
+
+	gsi_channel_program(gsi, channel_id, evt_ring_id, true);
+	gsi_ring_init(&channel->ring);
+
+	channel->user_data = user_data;
+	atomic_inc(&gsi->channel_count);
+
+	return 0;
+
+err_mutex_unlock:
+	mutex_unlock(&gsi->mutex);
+	kfree(user_data);
+err_ring_free:
+	gsi_ring_free(&channel->ring);
+err_evt_ring_free:
+	gsi_evt_ring_dealloc(gsi, evt_ring_id);
+
+	return ret;
+}
+
+static void __gsi_channel_scratch_write(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	struct gsi_gpi_channel_scratch *gpi;
+	union gsi_channel_scratch scr = { };
+	u32 val;
+
+	gpi = &scr.gpi;
+	/* See comments above definition of gsi_gpi_channel_scratch */
+	gpi->max_outstanding_tre = channel->tlv_count * GSI_RING_ELEMENT_SIZE;
+	gpi->outstanding_threshold = 2 * GSI_RING_ELEMENT_SIZE;
+
+	val = scr.data.word1;
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_0_OFFS(channel_id));
+
+	val = scr.data.word2;
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_1_OFFS(channel_id));
+
+	val = scr.data.word3;
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_2_OFFS(channel_id));
+
+	/* We must preserve the upper 16 bits of the last scratch
+	 * register.  The next sequence assumes those bits remain
+	 * unchanged between the read and the write.
+	 */
+	val = gsi_readl(gsi, GSI_CH_C_SCRATCH_3_OFFS(channel_id));
+	val = (scr.data.word4 & GENMASK(31, 16)) | (val & GENMASK(15, 0));
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_3_OFFS(channel_id));
+}
+
+void gsi_channel_scratch_write(struct gsi *gsi, u32 channel_id, u32 tlv_count)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+
+	channel->tlv_count = tlv_count;
+
+	mutex_lock(&channel->mutex);
+
+	__gsi_channel_scratch_write(gsi, channel_id);
+
+	mutex_unlock(&channel->mutex);
+}
+
+int gsi_channel_start(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+
+	if (channel->state != GSI_CHANNEL_STATE_ALLOCATED &&
+	    channel->state != GSI_CHANNEL_STATE_STOP_IN_PROC &&
+	    channel->state != GSI_CHANNEL_STATE_STOPPED) {
+		ipa_err("bad state %d\n", channel->state);
+		return -ENOTSUPP;
+	}
+
+	mutex_lock(&gsi->mutex);
+
+	gsi->ch_dbg[channel_id].ch_start++;
+
+	if (!channel_command(gsi, channel_id, GSI_CH_START)) {
+		mutex_unlock(&gsi->mutex);
+		return -ETIMEDOUT;
+	}
+	if (channel->state != GSI_CHANNEL_STATE_STARTED) {
+		ipa_err("channel %u unexpected state %u\n", channel_id,
+			channel->state);
+		ipa_bug();
+	}
+
+	mutex_unlock(&gsi->mutex);
+
+	return 0;
+}
+
+int gsi_channel_stop(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	int ret;
+
+	if (channel->state == GSI_CHANNEL_STATE_STOPPED)
+		return 0;
+
+	if (channel->state != GSI_CHANNEL_STATE_STARTED &&
+	    channel->state != GSI_CHANNEL_STATE_STOP_IN_PROC &&
+	    channel->state != GSI_CHANNEL_STATE_ERROR) {
+		ipa_err("bad state %d\n", channel->state);
+		return -ENOTSUPP;
+	}
+
+	mutex_lock(&gsi->mutex);
+
+	gsi->ch_dbg[channel_id].ch_stop++;
+
+	if (!channel_command(gsi, channel_id, GSI_CH_STOP)) {
+		/* check channel state here in case the channel is stopped but
+		 * the interrupt was not handled yet.
+		 */
+		channel->state = gsi_channel_state(gsi, channel_id);
+		if (channel->state == GSI_CHANNEL_STATE_STOPPED) {
+			ret = 0;
+			goto free_lock;
+		}
+		ret = -ETIMEDOUT;
+		goto free_lock;
+	}
+
+	if (channel->state != GSI_CHANNEL_STATE_STOPPED &&
+	    channel->state != GSI_CHANNEL_STATE_STOP_IN_PROC) {
+		ipa_err("channel %u unexpected state %u\n", channel_id,
+			channel->state);
+		ret = -EBUSY;
+		goto free_lock;
+	}
+
+	if (channel->state == GSI_CHANNEL_STATE_STOP_IN_PROC) {
+		ipa_err("channel %u busy try again\n", channel_id);
+		ret = -EAGAIN;
+		goto free_lock;
+	}
+
+	ret = 0;
+
+free_lock:
+	mutex_unlock(&gsi->mutex);
+
+	return ret;
+}
+
+int gsi_channel_reset(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_id;
+	bool reset_done;
+
+	if (channel->state != GSI_CHANNEL_STATE_STOPPED) {
+		ipa_err("bad state %d\n", channel->state);
+		return -ENOTSUPP;
+	}
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+	reset_done = false;
+	mutex_lock(&gsi->mutex);
+reset:
+
+	gsi->ch_dbg[channel_id].ch_reset++;
+
+	if (!channel_command(gsi, channel_id, GSI_CH_RESET)) {
+		mutex_unlock(&gsi->mutex);
+		return -ETIMEDOUT;
+	}
+
+	if (channel->state != GSI_CHANNEL_STATE_ALLOCATED) {
+		ipa_err("channel_id %u unexpected state %u\n", channel_id,
+			channel->state);
+		ipa_bug();
+	}
+
+	/* workaround: reset GSI producers again */
+	if (channel->from_ipa && !reset_done) {
+		usleep_range(GSI_RESET_WA_MIN_SLEEP, GSI_RESET_WA_MAX_SLEEP);
+		reset_done = true;
+		goto reset;
+	}
+
+	gsi_channel_program(gsi, channel_id, evt_ring_id, true);
+	gsi_ring_init(&channel->ring);
+
+	/* restore scratch */
+	__gsi_channel_scratch_write(gsi, channel_id);
+
+	mutex_unlock(&gsi->mutex);
+
+	return 0;
+}
+
+void gsi_channel_free(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_id;
+	bool completed;
+
+	ipa_bug_on(channel->state != GSI_CHANNEL_STATE_ALLOCATED);
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+	mutex_lock(&gsi->mutex);
+
+	gsi->ch_dbg[channel_id].ch_de_alloc++;
+
+	completed = channel_command(gsi, channel_id, GSI_CH_DE_ALLOC);
+	ipa_bug_on(!completed);
+
+	ipa_bug_on(channel->state != GSI_CHANNEL_STATE_NOT_ALLOCATED);
+
+	mutex_unlock(&gsi->mutex);
+
+	kfree(channel->user_data);
+	gsi_ring_free(&channel->ring);
+
+	gsi_evt_ring_dealloc(gsi, evt_ring_id);
+
+	memset(channel, 0, sizeof(*channel));
+
+	atomic_dec(&gsi->channel_count);
+}
+
+static u16 __gsi_query_ring_free_re(struct gsi_ring *ring)
+{
+	u64 delta;
+
+	if (ring->wp_local < ring->rp_local)
+		delta = ring->rp_local - ring->wp_local;
+	else
+		delta = ring->end - ring->wp_local + ring->rp_local;
+
+	return (u16)(delta / GSI_RING_ELEMENT_SIZE - 1);
+}
+
+int gsi_channel_queue(struct gsi *gsi, u32 channel_id, u16 num_xfers,
+		      struct gsi_xfer_elem *xfer, bool ring_db)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	unsigned long flags;
+	u32 i;
+
+	spin_lock_irqsave(&channel->evt_ring->ring.slock, flags);
+
+	if (num_xfers > __gsi_query_ring_free_re(&channel->ring)) {
+		spin_unlock_irqrestore(&channel->evt_ring->ring.slock, flags);
+		ipa_err("no space for %u-element transfer on ch %u\n",
+			num_xfers, channel_id);
+
+		return -ENOSPC;
+	}
+
+	for (i = 0; i < num_xfers; i++) {
+		struct gsi_tre *tre_ptr;
+		u16 idx = ring_wp_local_index(&channel->ring);
+
+		channel->user_data[idx] = xfer[i].user_data;
+
+		tre_ptr = ipa_dma_phys_to_virt(&channel->ring.mem,
+						  channel->ring.wp_local);
+
+		tre_ptr->buffer_ptr = xfer[i].addr;
+		tre_ptr->buf_len = xfer[i].len_opcode;
+		tre_ptr->bei = xfer[i].flags & GSI_XFER_FLAG_BEI ? 1 : 0;
+		tre_ptr->ieot = xfer[i].flags & GSI_XFER_FLAG_EOT ? 1 : 0;
+		tre_ptr->ieob = xfer[i].flags & GSI_XFER_FLAG_EOB ? 1 : 0;
+		tre_ptr->chain = xfer[i].flags & GSI_XFER_FLAG_CHAIN ? 1 : 0;
+
+		if (xfer[i].type == GSI_XFER_ELEM_DATA)
+			tre_ptr->re_type = GSI_RE_XFER;
+		else if (xfer[i].type == GSI_XFER_ELEM_IMME_CMD)
+			tre_ptr->re_type = GSI_RE_IMMD_CMD;
+		else if (xfer[i].type == GSI_XFER_ELEM_NOP)
+			tre_ptr->re_type = GSI_RE_NOP;
+		else
+			ipa_bug_on("invalid xfer type");
+
+		ring_wp_local_inc(&channel->ring);
+	}
+
+	wmb();	/* Ensure TRE is set before ringing doorbell */
+
+	if (ring_db)
+		gsi_channel_doorbell(gsi, channel);
+
+	spin_unlock_irqrestore(&channel->evt_ring->ring.slock, flags);
+
+	return 0;
+}
+
+int gsi_channel_poll(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	struct gsi_evt_ring *evt_ring;
+	unsigned long flags;
+	u32 evt_ring_id;
+	int size;
+
+	evt_ring = channel->evt_ring;
+	evt_ring_id = gsi_evt_ring_id(gsi, evt_ring);
+
+	spin_lock_irqsave(&evt_ring->ring.slock, flags);
+
+	/* update rp to see of we have anything new to process */
+	if (evt_ring->ring.rp == evt_ring->ring.rp_local) {
+		u32 val;
+
+		val = gsi_readl(gsi, GSI_EV_CH_E_CNTXT_4_OFFS(evt_ring_id));
+		evt_ring->ring.rp = channel->ring.rp & GENMASK_ULL(63, 32);
+		evt_ring->ring.rp |= val;
+	}
+
+	if (evt_ring->ring.rp != evt_ring->ring.rp_local) {
+		struct gsi_xfer_compl_evt *evt;
+
+		evt = ipa_dma_phys_to_virt(&evt_ring->ring.mem,
+					   evt_ring->ring.rp_local);
+		size = gsi_channel_process(gsi, evt, false);
+
+		ring_rp_local_inc(&evt_ring->ring);
+		ring_wp_local_inc(&evt_ring->ring); /* recycle element */
+	} else {
+		size = -ENOENT;
+	}
+
+	spin_unlock_irqrestore(&evt_ring->ring.slock, flags);
+
+	return size;
+}
+
+static void gsi_channel_mode_set(struct gsi *gsi, u32 channel_id, bool polling)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	unsigned long flags;
+	u32 evt_ring_id;
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+
+	spin_lock_irqsave(&gsi->slock, flags);
+
+	if (polling)
+		gsi_irq_disable_event(gsi, evt_ring_id);
+	else
+		gsi_irq_enable_event(gsi, evt_ring_id);
+	atomic_set(&channel->poll_mode, polling ? 1 : 0);
+
+	spin_unlock_irqrestore(&gsi->slock, flags);
+}
+
+void gsi_channel_intr_enable(struct gsi *gsi, u32 channel_id)
+{
+	gsi_channel_mode_set(gsi, channel_id, false);
+}
+
+void gsi_channel_intr_disable(struct gsi *gsi, u32 channel_id)
+{
+	gsi_channel_mode_set(gsi, channel_id, true);
+}
+
+void gsi_channel_config(struct gsi *gsi, u32 channel_id, bool doorbell_enable)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_id;
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+
+	mutex_lock(&channel->mutex);
+
+	gsi_channel_program(gsi, channel_id, evt_ring_id, doorbell_enable);
+	gsi_ring_init(&channel->ring);
+
+	/* restore scratch */
+	__gsi_channel_scratch_write(gsi, channel_id);
+	mutex_unlock(&channel->mutex);
+}
+
+/* Initialize GSI driver */
+struct gsi *gsi_init(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	resource_size_t size;
+	struct gsi *gsi;
+	int irq;
+
+	/* Get GSI memory range and map it */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gsi");
+	if (!res) {
+		ipa_err("missing \"gsi\" property in DTB\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	size = resource_size(res);
+	if (res->start > U32_MAX || size > U32_MAX) {
+		ipa_err("\"gsi\" values out of range\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	/* Get IPA GSI IRQ number */
+	irq = platform_get_irq_byname(pdev, "gsi");
+	if (irq < 0) {
+		ipa_err("failed to get gsi IRQ!\n");
+		return ERR_PTR(irq);
+	}
+
+	gsi = kzalloc(sizeof(*gsi), GFP_KERNEL);
+	if (!gsi)
+		return ERR_PTR(-ENOMEM);
+
+	gsi->base = devm_ioremap_nocache(dev, res->start, size);
+	if (!gsi->base) {
+		kfree(gsi);
+
+		return ERR_PTR(-ENOMEM);
+	}
+	gsi->dev = dev;
+	gsi->phys = (u32)res->start;
+	gsi->irq = irq;
+	spin_lock_init(&gsi->slock);
+	mutex_init(&gsi->mutex);
+	atomic_set(&gsi->channel_count, 0);
+	atomic_set(&gsi->evt_ring_count, 0);
+
+	return gsi;
+}
diff --git a/drivers/net/ipa/gsi.h b/drivers/net/ipa/gsi.h
new file mode 100644
index 000000000000..497f67cc6f80
--- /dev/null
+++ b/drivers/net/ipa/gsi.h
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _GSI_H_
+#define _GSI_H_
+
+#include <linux/types.h>
+#include <linux/platform_device.h>
+
+#define GSI_RING_ELEMENT_SIZE	16	/* bytes (channel or event ring) */
+
+/**
+ * enum gsi_xfer_flag - Transfer element flag values.
+ * @GSI_XFER_FLAG_CHAIN:	Not the last element in a transaction.
+ * @GSI_XFER_FLAG_EOB:		Generate event interrupt when complete.
+ * @GSI_XFER_FLAG_EOT:		Interrupt on end of transfer condition.
+ * @GSI_XFER_FLAG_BEI:		Block (do not generate) event interrupt.
+ *
+ * Normally an event generated by completion of a transfer will cause
+ * the AP to be interrupted; the BEI flag prevents that.
+ */
+enum gsi_xfer_flag {
+	GSI_XFER_FLAG_CHAIN	= BIT(1),
+	GSI_XFER_FLAG_EOB	= BIT(2),
+	GSI_XFER_FLAG_EOT	= BIT(3),
+	GSI_XFER_FLAG_BEI	= BIT(4),
+};
+
+/**
+ * enum gsi_xfer_elem_type - Transfer element type.
+ * @GSI_XFER_ELEM_DATA:		Element represents a data transfer
+ * @GSI_XFER_ELEM_IMME_CMD:	Element contains an immediate command.
+ * @GSI_XFER_ELEM_NOP:		Element contans a no-op command.
+ */
+enum gsi_xfer_elem_type {
+	GSI_XFER_ELEM_DATA,
+	GSI_XFER_ELEM_IMME_CMD,
+	GSI_XFER_ELEM_NOP,
+};
+
+/**
+ * gsi_xfer_elem - Description of a single transfer.
+ * @addr:	Physical address of a buffer for data or immediate commands.
+ * @len_opcode:	Length of the data buffer, or enum ipahal_imm_cmd opcode
+ * @flags:	Flags for the transfer
+ * @type:	Command type (immediate command, data transfer NOP)
+ * @user_data:	Data maintained for (but unused by) the transfer element.
+ */
+struct gsi_xfer_elem {
+	u64 addr;
+	u16 len_opcode;
+	enum gsi_xfer_flag flags;
+	enum gsi_xfer_elem_type type;
+	void *user_data;
+};
+
+struct gsi;
+
+/**
+ * gsi_init() - Initialize GSI subsystem
+ * @pdev:	IPA platform device, to look up resources
+ *
+ * This stage of initialization can occur before the GSI firmware
+ * has been loaded.
+ *
+ * Return:	GSI pointer to provide to other GSI functions.
+ */
+struct gsi *gsi_init(struct platform_device *pdev);
+
+/**
+ * gsi_device_init() - Initialize a GSI device
+ * @gsi:	GSI pointer returned by gsi_init()
+ *
+ * Initialize a GSI device.
+ *
+ * @Return:	0 if successful or a negative error code otherwise.
+ */
+int gsi_device_init(struct gsi *gsi);
+
+/**
+ * gsi_device_exit() - De-initialize a GSI device
+ * @gsi:	GSI pointer returned by gsi_init()
+ *
+ * This is the inverse of gsi_device_init()
+ */
+void gsi_device_exit(struct gsi *gsi);
+
+/**
+ * gsi_channel_alloc() - Allocate a GSI channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to allocate
+ * @channel_count: Number of transfer element slots in the channel
+ * @from_ipa:	Direction of data transfer (true: IPA->AP; false: AP->IPA)
+ * @priority:	Whether this channel will given prioroity
+ * @evt_ring_mult: Factor to use to get the number of elements in the
+ *		event ring associated with this channel
+ * @moderation:	Whether interrupt moderation should be enabled
+ * @notify_data: Pointer value to supply with notifications that
+ * 		occur because of events on this channel
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_alloc(struct gsi *gsi, u32 channel_id, u32 channel_count,
+		      bool from_ipa, bool priority, u32 evt_ring_mult,
+		      bool moderation, void *notify_data);
+
+/**
+ * gsi_channel_scratch_write() - Write channel scratch area
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel whose scratch area should be written
+ * @tlv_count:	The number of type-length-value the channel uses
+ */
+void gsi_channel_scratch_write(struct gsi *gsi, u32 channel_id, u32 tlv_count);
+
+/**
+ * gsi_channel_start() - Make a channel operational
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to start
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_start(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_stop() - Stop an operational channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to stop
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_stop(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_reset() - Reset a channel, to recover from error state
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to be reset
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_reset(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_free() - Release a previously-allocated channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to be freed
+ */
+void gsi_channel_free(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_config() - Configure a channel
+ * @gsi:		GSI pointer returned by gsi_init()
+ * @channel_id:		Channel to be configured
+ * @doorbell_enable:	Whether to enable hardware doorbell engine
+ */
+void gsi_channel_config(struct gsi *gsi, u32 channel_id, bool doorbell_enable);
+
+/**
+ * gsi_channel_poll() - Poll for a single completion on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to be polled
+ *
+ * @Return:	Byte transfer count if successful, or a negative error code
+ */
+int gsi_channel_poll(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_intr_enable() - Enable interrupts on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel whose interrupts should be enabled
+ */
+void gsi_channel_intr_enable(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_intr_disable() - Disable interrupts on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel whose interrupts should be disabled
+ */
+void gsi_channel_intr_disable(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_queue() - Queue transfer requests on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel on which transfers should be queued
+ * @num_xfers:	Number of transfer descriptors in the @xfer array
+ * @xfer:	Array of transfer descriptors
+ * @ring_db:	Whether to tell the hardware about these queued transfers
+ *
+ * @Return:	0 if successful, or a negative error code
+ */
+int gsi_channel_queue(struct gsi *gsi, u32 channel_id, u16 num_xfers,
+		      struct gsi_xfer_elem *xfer, bool ring_db);
+
+#endif /* _GSI_H_ */
diff --git a/drivers/net/ipa/gsi_reg.h b/drivers/net/ipa/gsi_reg.h
new file mode 100644
index 000000000000..fe5f98ef3840
--- /dev/null
+++ b/drivers/net/ipa/gsi_reg.h
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef __GSI_REG_H__
+#define __GSI_REG_H__
+
+/* The maximum allowed value of "n" for any N-parameterized macro below
+ * is 3.  The N value comes from the ipa_ees enumerated type.
+ *
+ * For GSI_INST_RAM_I_OFFS(), the "i" value supplied is an instruction
+ * offset (where each instruction is 32 bits wide).  The maximum offset
+ * value is 4095.
+ *
+ * Macros parameterized by (data) channel number supply a parameter "c".
+ * The maximum value of "c" is 30 (but the limit is hardware-dependent).
+ *
+ * Macros parameterized by event channel number supply a parameter "e".
+ * The maximum value of "e" is 15 (but the limit is hardware-dependent).
+ *
+ * For any K-parameterized macros, the "k" value will represent either an
+ * event ring id or a (data) channel id.  15 is the maximum value of
+ * "k" for event rings; otherwise the maximum is 30.
+ */
+#define GSI_CFG_OFFS				0x00000000
+#define GSI_ENABLE_FMASK			0x00000001
+#define MCS_ENABLE_FMASK			0x00000002
+#define DOUBLE_MCS_CLK_FREQ_FMASK		0x00000004
+#define UC_IS_MCS_FMASK				0x00000008
+#define PWR_CLPS_FMASK				0x00000010
+#define BP_MTRIX_DISABLE_FMASK			0x00000020
+
+#define GSI_MCS_CFG_OFFS			0x0000b000
+#define MCS_CFG_ENABLE_FMASK			0x00000001
+
+#define GSI_PERIPH_BASE_ADDR_LSB_OFFS		0x00000018
+
+#define GSI_PERIPH_BASE_ADDR_MSB_OFFS		0x0000001c
+
+#define GSI_IC_DISABLE_CHNL_BCK_PRS_LSB_OFFS	0x000000a0
+#define CHNL_REE_INT_FMASK			0x00000007
+#define CHNL_EV_ENG_INT_FMASK			0x00000040
+#define CHNL_INT_END_INT_FMASK			0x00001000
+#define CHNL_CSR_INT_FMASK			0x00fc0000
+#define CHNL_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_DISABLE_CHNL_BCK_PRS_MSB_OFFS	0x000000a4
+#define CHNL_TIMER_INT_FMASK			0x00000001
+#define CHNL_DB_ENG_INT_FMASK			0x00000040
+#define CHNL_RD_WR_INT_FMASK			0x00003000
+#define CHNL_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_GEN_EVNT_BCK_PRS_LSB_OFFS	0x000000a8
+#define EVT_REE_INT_FMASK			0x00000007
+#define EVT_EV_ENG_INT_FMASK			0x00000040
+#define EVT_INT_END_INT_FMASK			0x00001000
+#define EVT_CSR_INT_FMASK			0x00fc0000
+#define EVT_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_GEN_EVNT_BCK_PRS_MSB_OFFS	0x000000ac
+#define EVT_TIMER_INT_FMASK			0x00000001
+#define EVT_DB_ENG_INT_FMASK			0x00000040
+#define EVT_RD_WR_INT_FMASK			0x00003000
+#define EVT_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_GEN_INT_BCK_PRS_LSB_OFFS		0x000000b0
+#define INT_REE_INT_FMASK			0x00000007
+#define INT_EV_ENG_INT_FMASK			0x00000040
+#define INT_INT_END_INT_FMASK			0x00001000
+#define INT_CSR_INT_FMASK			0x00fc0000
+#define INT_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_GEN_INT_BCK_PRS_MSB_OFFS		0x000000b4
+#define INT_TIMER_INT_FMASK			0x00000001
+#define INT_DB_ENG_INT_FMASK			0x00000040
+#define INT_RD_WR_INT_FMASK			0x00003000
+#define INT_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_STOP_INT_MOD_BCK_PRS_LSB_OFFS	0x000000b8
+#define REE_INT_FMASK				0x00000007
+#define EV_ENG_INT_FMASK			0x00000040
+#define INT_END_INT_FMASK			0x00001000
+#define CSR_INT_FMASK				0x00fc0000
+#define TLV_INT_FMASK				0x3f000000
+
+#define GSI_IC_STOP_INT_MOD_BCK_PRS_MSB_OFFS	0x000000bc
+#define TIMER_INT_FMASK				0x00000001
+#define DB_ENG_INT_FMASK			0x00000040
+#define RD_WR_INT_FMASK				0x00003000
+#define UCONTROLLER_INT_FMASK			0x00fc0000
+
+#define GSI_IC_PROCESS_DESC_BCK_PRS_LSB_OFFS	0x000000c0
+#define DESC_REE_INT_FMASK			0x00000007
+#define DESC_EV_ENG_INT_FMASK			0x00000040
+#define DESC_INT_END_INT_FMASK			0x00001000
+#define DESC_CSR_INT_FMASK			0x00fc0000
+#define DESC_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_PROCESS_DESC_BCK_PRS_MSB_OFFS	0x000000c4
+#define DESC_TIMER_INT_FMASK			0x00000001
+#define DESC_DB_ENG_INT_FMASK			0x00000040
+#define DESC_RD_WR_INT_FMASK			0x00003000
+#define DESC_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_TLV_STOP_BCK_PRS_LSB_OFFS	0x000000c8
+#define STOP_REE_INT_FMASK			0x00000007
+#define STOP_EV_ENG_INT_FMASK			0x00000040
+#define STOP_INT_END_INT_FMASK			0x00001000
+#define STOP_CSR_INT_FMASK			0x00fc0000
+#define STOP_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_TLV_STOP_BCK_PRS_MSB_OFFS	0x000000cc
+#define STOP_TIMER_INT_FMASK			0x00000001
+#define STOP_DB_ENG_INT_FMASK			0x00000040
+#define STOP_RD_WR_INT_FMASK			0x00003000
+#define STOP_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_TLV_RESET_BCK_PRS_LSB_OFFS	0x000000d0
+#define RST_REE_INT_FMASK			0x00000007
+#define RST_EV_ENG_INT_FMASK			0x00000040
+#define RST_INT_END_INT_FMASK			0x00001000
+#define RST_CSR_INT_FMASK			0x00fc0000
+#define RST_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_TLV_RESET_BCK_PRS_MSB_OFFS	0x000000d4
+#define RST_TIMER_INT_FMASK			0x00000001
+#define RST_DB_ENG_INT_FMASK			0x00000040
+#define RST_RD_WR_INT_FMASK			0x00003000
+#define RST_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_RGSTR_TIMER_BCK_PRS_LSB_OFFS	0x000000d8
+#define TMR_REE_INT_FMASK			0x00000007
+#define TMR_EV_ENG_INT_FMASK			0x00000040
+#define TMR_INT_END_INT_FMASK			0x00001000
+#define TMR_CSR_INT_FMASK			0x00fc0000
+#define TMR_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_RGSTR_TIMER_BCK_PRS_MSB_OFFS	0x000000dc
+#define TMR_TIMER_INT_FMASK			0x00000001
+#define TMR_DB_ENG_INT_FMASK			0x00000040
+#define TMR_RD_WR_INT_FMASK			0x00003000
+#define TMR_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_READ_BCK_PRS_LSB_OFFS		0x000000e0
+#define RD_REE_INT_FMASK			0x00000007
+#define RD_EV_ENG_INT_FMASK			0x00000040
+#define RD_INT_END_INT_FMASK			0x00001000
+#define RD_CSR_INT_FMASK			0x00fc0000
+#define RD_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_READ_BCK_PRS_MSB_OFFS		0x000000e4
+#define RD_TIMER_INT_FMASK			0x00000001
+#define RD_DB_ENG_INT_FMASK			0x00000040
+#define RD_RD_WR_INT_FMASK			0x00003000
+#define RD_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_WRITE_BCK_PRS_LSB_OFFS		0x000000e8
+#define WR_REE_INT_FMASK			0x00000007
+#define WR_EV_ENG_INT_FMASK			0x00000040
+#define WR_INT_END_INT_FMASK			0x00001000
+#define WR_CSR_INT_FMASK			0x00fc0000
+#define WR_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_WRITE_BCK_PRS_MSB_OFFS		0x000000ec
+#define WR_TIMER_INT_FMASK			0x00000001
+#define WR_DB_ENG_INT_FMASK			0x00000040
+#define WR_RD_WR_INT_FMASK			0x00003000
+#define WR_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_UCONTROLLER_GPR_BCK_PRS_LSB_OFFS	0x000000f0
+#define UC_REE_INT_FMASK			0x00000007
+#define UC_EV_ENG_INT_FMASK			0x00000040
+#define UC_INT_END_INT_FMASK			0x00001000
+#define UC_CSR_INT_FMASK			0x00fc0000
+#define UC_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_UCONTROLLER_GPR_BCK_PRS_MSB_OFFS	0x000000f4
+#define UC_TIMER_INT_FMASK			0x00000001
+#define UC_DB_ENG_INT_FMASK			0x00000040
+#define UC_RD_WR_INT_FMASK			0x00003000
+#define UC_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IRAM_PTR_CH_CMD_OFFS		0x00000400
+#define CMD_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_EE_GENERIC_CMD_OFFS	0x00000404
+#define EE_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_CH_DB_OFFS			0x00000418
+#define CH_DB_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_EV_DB_OFFS			0x0000041c
+#define EV_DB_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_NEW_RE_OFFS		0x00000420
+#define NEW_RE_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_CH_DIS_COMP_OFFS		0x00000424
+#define DIS_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_CH_EMPTY_OFFS		0x00000428
+#define EMPTY_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_EVENT_GEN_COMP_OFFS	0x0000042c
+#define EVT_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_PERIPH_IF_TLV_IN_0_OFFS	0x00000430
+#define IN_0_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_PERIPH_IF_TLV_IN_2_OFFS	0x00000434
+#define IN_2_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_PERIPH_IF_TLV_IN_1_OFFS	0x00000438
+#define IN_1_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_TIMER_EXPIRED_OFFS		0x0000043c
+#define TMR_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_WRITE_ENG_COMP_OFFS	0x00000440
+#define WR_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_READ_ENG_COMP_OFFS		0x00000444
+#define RD_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_UC_GP_INT_OFFS		0x00000448
+#define UC_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_INT_MOD_STOPPED_OFFS	0x0000044c
+#define STOP_IRAM_PTR_FMASK			0x00000fff
+
+/* Max value of I for the GSI_INST_RAM_I_OFFS() is 4095 */
+#define GSI_INST_RAM_I_OFFS(i)			(0x00004000 + 0x0004 * (i))
+#define INST_BYTE_0_FMASK			0x000000ff
+#define INST_BYTE_1_FMASK			0x0000ff00
+#define INST_BYTE_2_FMASK			0x00ff0000
+#define INST_BYTE_3_FMASK			0xff000000
+
+#define GSI_CH_C_CNTXT_0_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_0_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_0_OFFS(c, n) \
+					(0x0001c000 + 0x4000 * (n) + 0x80 * (c))
+#define CHTYPE_PROTOCOL_FMASK			0x00000007
+#define CHTYPE_DIR_FMASK			0x00000008
+#define EE_FMASK				0x000000f0
+#define CHID_FMASK				0x00001f00
+#define ERINDEX_FMASK				0x0007c000
+#define CHSTATE_FMASK				0x00f00000
+#define ELEMENT_SIZE_FMASK			0xff000000
+
+#define GSI_CH_C_CNTXT_1_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_1_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_1_OFFS(c, n) \
+					(0x0001c004 + 0x4000 * (n) + 0x80 * (c))
+#define R_LENGTH_FMASK				0x0000ffff
+
+#define GSI_CH_C_CNTXT_2_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_2_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_2_OFFS(c, n) \
+					(0x0001c008 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_CNTXT_3_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_3_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_3_OFFS(c, n) \
+					(0x0001c00c + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_CNTXT_4_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_4_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_4_OFFS(c, n) \
+					(0x0001c010 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_CNTXT_6_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_6_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_6_OFFS(c, n) \
+					(0x0001c018 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_QOS_OFFS(c)	GSI_EE_N_CH_C_QOS_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_QOS_OFFS(c, n)	(0x0001c05c + 0x4000 * (n) + 0x80 * (c))
+#define WRR_WEIGHT_FMASK			0x0000000f
+#define MAX_PREFETCH_FMASK			0x00000100
+#define USE_DB_ENG_FMASK			0x00000200
+
+#define GSI_CH_C_SCRATCH_0_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_0_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_0_OFFS(c, n) \
+					(0x0001c060 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_SCRATCH_1_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_1_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_1_OFFS(c, n) \
+					(0x0001c064 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_SCRATCH_2_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_2_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_2_OFFS(c, n) \
+					(0x0001c068 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_SCRATCH_3_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_3_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_3_OFFS(c, n) \
+					(0x0001c06c + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_EV_CH_E_CNTXT_0_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_0_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_0_OFFS(e, n) \
+					(0x0001d000 + 0x4000 * (n) + 0x80 * (e))
+#define EV_CHTYPE_FMASK				0x0000000f
+#define EV_EE_FMASK				0x000000f0
+#define EV_EVCHID_FMASK				0x0000ff00
+#define EV_INTYPE_FMASK				0x00010000
+#define EV_CHSTATE_FMASK			0x00f00000
+#define EV_ELEMENT_SIZE_FMASK			0xff000000
+
+#define GSI_EV_CH_E_CNTXT_1_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_1_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_1_OFFS(e, n) \
+					(0x0001d004 + 0x4000 * (n) + 0x80 * (e))
+#define EV_R_LENGTH_FMASK			0x0000ffff
+
+#define GSI_EV_CH_E_CNTXT_2_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_2_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_2_OFFS(e, n) \
+					(0x0001d008 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_3_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_3_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_3_OFFS(e, n) \
+					(0x0001d00c + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_4_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_4_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_4_OFFS(e, n) \
+					(0x0001d010 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_8_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_8_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_8_OFFS(e, n) \
+					(0x0001d020 + 0x4000 * (n) + 0x80 * (e))
+#define MODT_FMASK				0x0000ffff
+#define MODC_FMASK				0x00ff0000
+#define MOD_CNT_FMASK				0xff000000
+
+#define GSI_EV_CH_E_CNTXT_9_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_9_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_9_OFFS(e, n) \
+					(0x0001d024 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_10_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_10_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_10_OFFS(e, n) \
+					(0x0001d028 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_11_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_11_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_11_OFFS(e, n) \
+					(0x0001d02c + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_12_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_12_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_12_OFFS(e, n) \
+					(0x0001d030 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_13_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_13_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_13_OFFS(e, n) \
+					(0x0001d034 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_SCRATCH_0_OFFS(e) \
+				GSI_EE_N_EV_CH_E_SCRATCH_0_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_SCRATCH_0_OFFS(e, n) \
+					(0x0001d048 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_SCRATCH_1_OFFS(e) \
+				GSI_EE_N_EV_CH_E_SCRATCH_1_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_SCRATCH_1_OFFS(e, n) \
+					(0x0001d04c + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_CH_C_DOORBELL_0_OFFS(c) \
+				GSI_EE_N_CH_C_DOORBELL_0_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_DOORBELL_0_OFFS(c, n) \
+					(0x0001e000 + 0x4000 * (n) + 0x08 * (c))
+
+#define GSI_CH_C_DOORBELL_1_OFFS(c) \
+				GSI_EE_N_CH_C_DOORBELL_1_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_DOORBELL_1_OFFS(c, n) \
+					(0x0001e004 + 0x4000 * (n) + 0x08 * (c))
+
+#define GSI_EV_CH_E_DOORBELL_0_OFFS(e) \
+				GSI_EE_N_EV_CH_E_DOORBELL_0_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_DOORBELL_0_OFFS(e, n) \
+					(0x0001e100 + 0x4000 * (n) + 0x08 * (e))
+
+#define GSI_EV_CH_E_DOORBELL_1_OFFS(e) \
+				GSI_EE_N_EV_CH_E_DOORBELL_1_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_DOORBELL_1_OFFS(e, n) \
+					(0x0001e104 + 0x4000 * (n) + 0x08 * (e))
+
+#define GSI_GSI_STATUS_OFFS	GSI_EE_N_GSI_STATUS_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_STATUS_OFFS(n)		(0x0001f000 + 0x4000 * (n))
+#define ENABLED_FMASK				0x00000001
+
+#define GSI_CH_CMD_OFFS		GSI_EE_N_CH_CMD_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CH_CMD_OFFS(n)			(0x0001f008 + 0x4000 * (n))
+#define CH_CHID_FMASK				0x000000ff
+#define CH_OPCODE_FMASK				0xff000000
+
+#define GSI_EV_CH_CMD_OFFS	GSI_EE_N_EV_CH_CMD_OFFS(IPA_EE_AP)
+#define GSI_EE_N_EV_CH_CMD_OFFS(n)		(0x0001f010 + 0x4000 * (n))
+#define EV_CHID_FMASK				0x000000ff
+#define EV_OPCODE_FMASK				0xff000000
+
+#define GSI_GSI_HW_PARAM_2_OFFS	GSI_EE_N_GSI_HW_PARAM_2_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_HW_PARAM_2_OFFS(n)		(0x0001f040 + 0x4000 * (n))
+#define IRAM_SIZE_FMASK				0x00000007
+#define NUM_CH_PER_EE_FMASK			0x000000f8
+#define NUM_EV_PER_EE_FMASK			0x00001f00
+#define GSI_CH_PEND_TRANSLATE_FMASK		0x00002000
+#define GSI_CH_FULL_LOGIC_FMASK			0x00004000
+#define IRAM_SIZE_ONE_KB_FVAL			0
+#define IRAM_SIZE_TWO_KB_FVAL			1
+
+#define GSI_GSI_SW_VERSION_OFFS	GSI_EE_N_GSI_SW_VERSION_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_SW_VERSION_OFFS(n)		(0x0001f044 + 0x4000 * (n))
+#define STEP_FMASK				0x0000ffff
+#define MINOR_FMASK				0x0fff0000
+#define MAJOR_FMASK				0xf0000000
+
+#define GSI_GSI_MCS_CODE_VER_OFFS \
+				GSI_EE_N_GSI_MCS_CODE_VER_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_MCS_CODE_VER_OFFS(n)	(0x0001f048 + 0x4000 * (n))
+
+#define GSI_CNTXT_TYPE_IRQ_OFFS	GSI_EE_N_CNTXT_TYPE_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_TYPE_IRQ_OFFS(n)		(0x0001f080 + 0x4000 * (n))
+#define CH_CTRL_FMASK				0x00000001
+#define EV_CTRL_FMASK				0x00000002
+#define GLOB_EE_FMASK				0x00000004
+#define IEOB_FMASK				0x00000008
+#define INTER_EE_CH_CTRL_FMASK			0x00000010
+#define INTER_EE_EV_CTRL_FMASK			0x00000020
+#define GENERAL_FMASK				0x00000040
+
+#define GSI_CNTXT_TYPE_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_TYPE_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_TYPE_IRQ_MSK_OFFS(n)	(0x0001f088 + 0x4000 * (n))
+#define MSK_CH_CTRL_FMASK			0x00000001
+#define MSK_EV_CTRL_FMASK			0x00000002
+#define MSK_GLOB_EE_FMASK			0x00000004
+#define MSK_IEOB_FMASK				0x00000008
+#define MSK_INTER_EE_CH_CTRL_FMASK		0x00000010
+#define MSK_INTER_EE_EV_CTRL_FMASK		0x00000020
+#define MSK_GENERAL_FMASK			0x00000040
+
+#define GSI_CNTXT_SRC_CH_IRQ_OFFS \
+				GSI_EE_N_CNTXT_SRC_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_CH_IRQ_OFFS(n)	(0x0001f090 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_EV_CH_IRQ_OFFS \
+				GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_OFFS(n)	(0x0001f094 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_CH_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_SRC_CH_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_CH_IRQ_MSK_OFFS(n)	(0x0001f098 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS(n) (0x0001f09c + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_CH_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_SRC_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_CH_IRQ_CLR_OFFS(n)	(0x0001f0a0 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS(n) (0x0001f0a4 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_IEOB_IRQ_OFFS \
+				GSI_EE_N_CNTXT_SRC_IEOB_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_IEOB_IRQ_OFFS(n)	(0x0001f0b0 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_SRC_IEOB_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_IEOB_IRQ_MSK_OFFS(n)	(0x0001f0b8 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_SRC_IEOB_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_IEOB_IRQ_CLR_OFFS(n)	(0x0001f0c0 + 0x4000 * (n))
+
+#define GSI_CNTXT_GLOB_IRQ_STTS_OFFS \
+				GSI_EE_N_CNTXT_GLOB_IRQ_STTS_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GLOB_IRQ_STTS_OFFS(n)	(0x0001f100 + 0x4000 * (n))
+#define ERROR_INT_FMASK				0x00000001
+#define GP_INT1_FMASK				0x00000002
+#define GP_INT2_FMASK				0x00000004
+#define GP_INT3_FMASK				0x00000008
+
+#define GSI_CNTXT_GLOB_IRQ_EN_OFFS \
+				GSI_EE_N_CNTXT_GLOB_IRQ_EN_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GLOB_IRQ_EN_OFFS(n)	(0x0001f108 + 0x4000 * (n))
+#define EN_ERROR_INT_FMASK			0x00000001
+#define EN_GP_INT1_FMASK			0x00000002
+#define EN_GP_INT2_FMASK			0x00000004
+#define EN_GP_INT3_FMASK			0x00000008
+
+#define GSI_CNTXT_GLOB_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_GLOB_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GLOB_IRQ_CLR_OFFS(n)	(0x0001f110 + 0x4000 * (n))
+#define CLR_ERROR_INT_FMASK			0x00000001
+#define CLR_GP_INT1_FMASK			0x00000002
+#define CLR_GP_INT2_FMASK			0x00000004
+#define CLR_GP_INT3_FMASK			0x00000008
+
+#define GSI_CNTXT_GSI_IRQ_STTS_OFFS \
+				GSI_EE_N_CNTXT_GSI_IRQ_STTS_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GSI_IRQ_STTS_OFFS(n)	(0x0001f118 + 0x4000 * (n))
+#define BREAK_POINT_FMASK			0x00000001
+#define BUS_ERROR_FMASK				0x00000002
+#define CMD_FIFO_OVRFLOW_FMASK			0x00000004
+#define MCS_STACK_OVRFLOW_FMASK			0x00000008
+
+#define GSI_CNTXT_GSI_IRQ_EN_OFFS \
+				GSI_EE_N_CNTXT_GSI_IRQ_EN_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GSI_IRQ_EN_OFFS(n)	(0x0001f120 + 0x4000 * (n))
+#define EN_BREAK_POINT_FMASK			0x00000001
+#define EN_BUS_ERROR_FMASK			0x00000002
+#define EN_CMD_FIFO_OVRFLOW_FMASK		0x00000004
+#define EN_MCS_STACK_OVRFLOW_FMASK		0x00000008
+
+#define GSI_CNTXT_GSI_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_GSI_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GSI_IRQ_CLR_OFFS(n)	(0x0001f128 + 0x4000 * (n))
+#define CLR_BREAK_POINT_FMASK			0x00000001
+#define CLR_BUS_ERROR_FMASK			0x00000002
+#define CLR_CMD_FIFO_OVRFLOW_FMASK		0x00000004
+#define CLR_MCS_STACK_OVRFLOW_FMASK		0x00000008
+
+#define GSI_EE_N_CNTXT_INTSET_OFFS(n)		(0x0001f180 + 0x4000 * (n))
+#define INTYPE_FMASK				0x00000001
+#define GSI_CNTXT_INTSET_OFFS	GSI_EE_N_CNTXT_INTSET_OFFS(IPA_EE_AP)
+
+#define GSI_ERROR_LOG_OFFS	GSI_EE_N_ERROR_LOG_OFFS(IPA_EE_AP)
+#define GSI_EE_N_ERROR_LOG_OFFS(n)		(0x0001f200 + 0x4000 * (n))
+
+#define GSI_ERROR_LOG_CLR_OFFS	GSI_EE_N_ERROR_LOG_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_ERROR_LOG_CLR_OFFS(n)		(0x0001f210 + 0x4000 * (n))
+
+#define GSI_INTER_EE_SRC_CH_IRQ_OFFS \
+				GSI_INTER_EE_N_SRC_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_CH_IRQ_OFFS(n)	(0x0000c018 + 0x1000 * (n))
+
+#define GSI_INTER_EE_SRC_EV_CH_IRQ_OFFS \
+				GSI_INTER_EE_N_SRC_EV_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_EV_CH_IRQ_OFFS(n)	(0x0000c01c + 0x1000 * (n))
+
+#define GSI_INTER_EE_SRC_CH_IRQ_CLR_OFFS \
+				GSI_INTER_EE_N_SRC_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_CH_IRQ_CLR_OFFS(n)	(0x0000c028 + 0x1000 * (n))
+
+#define GSI_INTER_EE_SRC_EV_CH_IRQ_CLR_OFFS \
+				GSI_INTER_EE_N_SRC_EV_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_EV_CH_IRQ_CLR_OFFS(n) (0x0000c02c + 0x1000 * (n))
+
+#endif	/* _GSI_REG_H__ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 03/12] soc: qcom: ipa: generic software interface
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch contains the code supporting the Generic Software
Interface (GSI) used by the IPA.  Although the GSI is an integral
part of the IPA, it provides a well-defined layer between the AP
subsystem (or, for that matter, the modem) and the IPA core.

The GSI code presents an abstract interface through which commands
and data transfers can be queued to be implemented on a channel.  A
hardware independent gsi_xfer_elem structure describes a single
transfer, and an array of these can be queued on a channel.  The
information in the gsi_xfer_elem is converted by the GSI layer into
the specific layout required by the hardware.

A channel has an associated event ring, through which completion of
channel commands can be signaled.  GSI channel commands are completed
in order, and may optionally generate an interrupt on completion.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/gsi.c     | 1685 +++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/gsi.h     |  195 +++++
 drivers/net/ipa/gsi_reg.h |  563 +++++++++++++
 3 files changed, 2443 insertions(+)
 create mode 100644 drivers/net/ipa/gsi.c
 create mode 100644 drivers/net/ipa/gsi.h
 create mode 100644 drivers/net/ipa/gsi_reg.h

diff --git a/drivers/net/ipa/gsi.c b/drivers/net/ipa/gsi.c
new file mode 100644
index 000000000000..348ee1fc1bf5
--- /dev/null
+++ b/drivers/net/ipa/gsi.c
@@ -0,0 +1,1685 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include <linux/bitfield.h>
+#include <linux/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include "gsi.h"
+#include "gsi_reg.h"
+#include "ipa_dma.h"
+#include "ipa_i.h"	/* ipa_err() */
+
+/**
+ * DOC: The Role of GSI in IPA Operation
+ *
+ * The generic software interface (GSI) is an integral component of
+ * the IPA, providing a well-defined layer between the AP subsystem
+ * (or, for that matter, the modem) and the IPA core::
+ *
+ *  ----------   -------------   ---------
+ *  |        |   |G|       |G|   |       |
+ *  |  APSS  |===|S|  IPA  |S|===| Modem |
+ *  |        |   |I|       |I|   |       |
+ *  ----------   -------------   ---------
+ *
+ * In the above diagram, the APSS and Modem represent "execution
+ * environments" (EEs), which are independent operating environments
+ * that use the IPA for data transfer.
+ *
+ * Each EE uses a set of unidirectional GSI "channels," which allow
+ * transfer of data to or from the IPA.  A channel is implemented as a
+ * ring buffer, with a DRAM-resident array of "transfer elements" (TREs)
+ * available to describe transfers to or from other EEs through the IPA.
+ * A transfer element can also contain an immediate command, requesting
+ * the IPA perform actions other than data transfer.
+ *
+ * Each transfer element refers to a block of data--also located DRAM.
+ * After writing one or more TREs to a channel, the writer (either the
+ * IPA or an EE) writes a doorbell register to inform the receiving side
+ * how many elements have been written.  Writing to a doorbell register
+ * triggers an interrupt on the receiver.
+ *
+ * Each channel has a GSI "event ring" associated with it.  An event
+ * ring is implemented very much like a channel ring, but is always
+ * directed from the IPA to an EE.  The IPA notifies an EE (such as
+ * the AP) about channel events by adding an entry to the event ring
+ * associated with the channel; when it writes the event ring's
+ * doorbell register the EE will be interrupted.
+ *
+ * A transfer element has a set of flags.  One flag indicates whether
+ * the completion of the transfer operation generates a channel event.
+ * Another flag allows transfer elements to be chained together,
+ * forming a single logical transaction.  These flags are used to
+ * control whether and when interrupts are generated to signal
+ * completion of a channel transfer.
+ *
+ * Elements in channel and event rings are completed (or consumed)
+ * strictly in order.  Completion of one entry implies the completion
+ * of all preceding entries.  A single completion interrupt can
+ * therefore be used to communicate the completion of many transfers.
+ */
+
+#define GSI_RING_ELEMENT_SIZE	16	/* bytes (channel or event ring) */
+
+#define GSI_CHAN_MAX		14
+#define GSI_EVT_RING_MAX	10
+
+/* Delay period if interrupt moderation is in effect */
+#define IPA_GSI_EVT_RING_INT_MODT	(32 * 1) /* 1ms under 32KHz clock */
+
+#define GSI_CMD_TIMEOUT		msecs_to_jiffies(5 * MSEC_PER_SEC)
+
+#define GSI_MHI_ER_START	10	/* First reserved event number */
+#define GSI_MHI_ER_END		16	/* Last reserved event number */
+
+#define GSI_RESET_WA_MIN_SLEEP	1000	/* microseconds */
+#define GSI_RESET_WA_MAX_SLEEP	2000	/* microseconds */
+
+#define GSI_MAX_PREFETCH	0	/* 0 means 1 segment; 1 means 2 */
+
+#define GSI_ISR_MAX_ITER	50
+
+/* Hardware values from the error log register code field */
+enum gsi_err_code {
+	GSI_INVALID_TRE_ERR			= 0x1,
+	GSI_OUT_OF_BUFFERS_ERR			= 0x2,
+	GSI_OUT_OF_RESOURCES_ERR		= 0x3,
+	GSI_UNSUPPORTED_INTER_EE_OP_ERR		= 0x4,
+	GSI_EVT_RING_EMPTY_ERR			= 0x5,
+	GSI_NON_ALLOCATED_EVT_ACCESS_ERR	= 0x6,
+	GSI_HWO_1_ERR				= 0x8,
+};
+
+/* Hardware values used when programming an event ring context */
+enum gsi_evt_chtype {
+	GSI_EVT_CHTYPE_MHI_EV	= 0x0,
+	GSI_EVT_CHTYPE_XHCI_EV	= 0x1,
+	GSI_EVT_CHTYPE_GPI_EV	= 0x2,
+	GSI_EVT_CHTYPE_XDCI_EV	= 0x3,
+};
+
+/* Hardware values used when programming a channel context */
+enum gsi_channel_protocol {
+	GSI_CHANNEL_PROTOCOL_MHI	= 0x0,
+	GSI_CHANNEL_PROTOCOL_XHCI	= 0x1,
+	GSI_CHANNEL_PROTOCOL_GPI	= 0x2,
+	GSI_CHANNEL_PROTOCOL_XDCI	= 0x3,
+};
+
+/* Hardware values returned in a transfer completion event structure */
+enum gsi_channel_evt {
+	GSI_CHANNEL_EVT_INVALID		= 0x0,
+	GSI_CHANNEL_EVT_SUCCESS		= 0x1,
+	GSI_CHANNEL_EVT_EOT		= 0x2,
+	GSI_CHANNEL_EVT_OVERFLOW	= 0x3,
+	GSI_CHANNEL_EVT_EOB		= 0x4,
+	GSI_CHANNEL_EVT_OOB		= 0x5,
+	GSI_CHANNEL_EVT_DB_MODE		= 0x6,
+	GSI_CHANNEL_EVT_UNDEFINED	= 0x10,
+	GSI_CHANNEL_EVT_RE_ERROR	= 0x11,
+};
+
+/* Hardware values signifying the state of an event ring */
+enum gsi_evt_ring_state {
+	GSI_EVT_RING_STATE_NOT_ALLOCATED	= 0x0,
+	GSI_EVT_RING_STATE_ALLOCATED		= 0x1,
+	GSI_EVT_RING_STATE_ERROR		= 0xf,
+};
+
+/* Hardware values signifying the state of a channel */
+enum gsi_channel_state {
+	GSI_CHANNEL_STATE_NOT_ALLOCATED	= 0x0,
+	GSI_CHANNEL_STATE_ALLOCATED	= 0x1,
+	GSI_CHANNEL_STATE_STARTED	= 0x2,
+	GSI_CHANNEL_STATE_STOPPED	= 0x3,
+	GSI_CHANNEL_STATE_STOP_IN_PROC	= 0x4,
+	GSI_CHANNEL_STATE_ERROR		= 0xf,
+};
+
+struct gsi_ring {
+	spinlock_t slock;		/* protects wp, rp updates */
+	struct ipa_dma_mem mem;
+	u64 wp;
+	u64 rp;
+	u64 wp_local;
+	u64 rp_local;
+	u64 end;			/* physical addr past last element */
+};
+
+struct gsi_channel {
+	bool from_ipa;			/* true: IPA->AP; false: AP->IPA */
+	bool priority;		/* Does hardware give this channel priority? */
+	enum gsi_channel_state state;
+	struct gsi_ring ring;
+	void *notify_data;
+	void **user_data;
+	struct gsi_evt_ring *evt_ring;
+	struct mutex mutex;		/* protects channel_scratch updates */
+	struct completion compl;
+	atomic_t poll_mode;
+	u32 tlv_count;			/* # slots in TLV */
+};
+
+struct gsi_evt_ring {
+	bool moderation;
+	enum gsi_evt_ring_state state;
+	struct gsi_ring ring;
+	struct completion compl;
+	struct gsi_channel *channel;
+};
+
+struct ch_debug_stats {
+	unsigned long ch_allocate;
+	unsigned long ch_start;
+	unsigned long ch_stop;
+	unsigned long ch_reset;
+	unsigned long ch_de_alloc;
+	unsigned long ch_db_stop;
+	unsigned long cmd_completed;
+};
+
+struct gsi {
+	void __iomem *base;
+	struct device *dev;
+	u32 phys;
+	unsigned int irq;
+	bool irq_wake_enabled;
+	spinlock_t slock;	/* protects global register updates */
+	struct mutex mutex;	/* protects 1-at-a-time commands, evt_bmap */
+	atomic_t channel_count;
+	atomic_t evt_ring_count;
+	struct gsi_channel channel[GSI_CHAN_MAX];
+	struct ch_debug_stats ch_dbg[GSI_CHAN_MAX];
+	struct gsi_evt_ring evt_ring[GSI_EVT_RING_MAX];
+	unsigned long evt_bmap;
+	u32 channel_max;
+	u32 evt_ring_max;
+};
+
+/* Hardware values representing a transfer element type */
+enum gsi_re_type {
+	GSI_RE_XFER	= 0x2,
+	GSI_RE_IMMD_CMD	= 0x3,
+	GSI_RE_NOP	= 0x4,
+};
+
+struct gsi_tre {
+	u64 buffer_ptr;
+	u16 buf_len;
+	u16 rsvd1;
+	u8  chain	: 1,
+	    rsvd4	: 7;
+	u8  ieob	: 1,
+	    ieot	: 1,
+	    bei		: 1,
+	    rsvd3	: 5;
+	u8 re_type;
+	u8 rsvd2;
+} __packed;
+
+struct gsi_xfer_compl_evt {
+	u64 xfer_ptr;
+	u16 len;
+	u8 rsvd1;
+	u8 code;  /* see gsi_channel_evt */
+	u16 rsvd;
+	u8 type;
+	u8 chid;
+} __packed;
+
+/* Hardware values from the error log register error type field */
+enum gsi_err_type {
+	GSI_ERR_TYPE_GLOB	= 0x1,
+	GSI_ERR_TYPE_CHAN	= 0x2,
+	GSI_ERR_TYPE_EVT	= 0x3,
+};
+
+struct gsi_log_err {
+	u8  arg3	: 4,
+	    arg2	: 4;
+	u8  arg1	: 4,
+	    code	: 4;
+	u8  rsvd	: 3,
+	    virt_idx	: 5;
+	u8  err_type	: 4,
+	    ee		: 4;
+} __packed;
+
+/* Hardware values repreasenting a channel immediate command opcode */
+enum gsi_ch_cmd_opcode {
+	GSI_CH_ALLOCATE	= 0x0,
+	GSI_CH_START	= 0x1,
+	GSI_CH_STOP	= 0x2,
+	GSI_CH_RESET	= 0x9,
+	GSI_CH_DE_ALLOC	= 0xa,
+	GSI_CH_DB_STOP	= 0xb,
+};
+
+/* Hardware values repreasenting an event ring immediate command opcode */
+enum gsi_evt_ch_cmd_opcode {
+	GSI_EVT_ALLOCATE	= 0x0,
+	GSI_EVT_RESET		= 0x9,
+	GSI_EVT_DE_ALLOC	= 0xa,
+};
+
+/** gsi_gpi_channel_scratch - GPI protocol SW config area of channel scratch
+ *
+ * @max_outstanding_tre: Used for the prefetch management sequence by the
+ *			 sequencer. Defines the maximum number of allowed
+ *			 outstanding TREs in IPA/GSI (in Bytes). RE engine
+ *			 prefetch will be limited by this configuration. It
+ *			 is suggested to configure this value to IPA_IF
+ *			 channel TLV queue size times element size. To disable
+ *			 the feature in doorbell mode (DB Mode=1). Maximum
+ *			 outstanding TREs should be set to 64KB
+ *			 (or any value larger or equal to ring length . RLEN)
+ * @outstanding_threshold: Used for the prefetch management sequence by the
+ *			 sequencer. Defines the threshold (in Bytes) as to when
+ *			 to update the channel doorbell. Should be smaller than
+ *			 Maximum outstanding TREs. value. It is suggested to
+ *			 configure this value to 2 * element size.
+ */
+struct gsi_gpi_channel_scratch {
+	u64 rsvd1;
+	u16 rsvd2;
+	u16 max_outstanding_tre;
+	u16 rsvd3;
+	u16 outstanding_threshold;
+} __packed;
+
+/** gsi_channel_scratch - channel scratch SW config area */
+union gsi_channel_scratch {
+	struct gsi_gpi_channel_scratch gpi;
+	struct {
+		u32 word1;
+		u32 word2;
+		u32 word3;
+		u32 word4;
+	} data;
+} __packed;
+
+/* Read a value from the given offset into the I/O space defined in
+ * the GSI context.
+ */
+static u32 gsi_readl(struct gsi *gsi, u32 offset)
+{
+	return readl(gsi->base + offset);
+}
+
+/* Write the provided value to the given offset into the I/O space
+ * defined in the GSI context.
+ */
+static void gsi_writel(struct gsi *gsi, u32 v, u32 offset)
+{
+	writel(v, gsi->base + offset);
+}
+
+static void
+_gsi_irq_control_event(struct gsi *gsi, u32 evt_ring_id, bool enable)
+{
+	u32 mask = BIT(evt_ring_id);
+	u32 val;
+
+	val = gsi_readl(gsi, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+	if (enable)
+		val |= mask;
+	else
+		val &= ~mask;
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+}
+
+static void gsi_irq_disable_event(struct gsi *gsi, u32 evt_ring_id)
+{
+	_gsi_irq_control_event(gsi, evt_ring_id, false);
+}
+
+static void gsi_irq_enable_event(struct gsi *gsi, u32 evt_ring_id)
+{
+	_gsi_irq_control_event(gsi, evt_ring_id, true);
+}
+
+static void _gsi_irq_control_all(struct gsi *gsi, bool enable)
+{
+	u32 val = enable ? ~0 : 0;
+
+	/* Inter EE commands / interrupt are no supported. */
+	gsi_writel(gsi, val, GSI_CNTXT_TYPE_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_CH_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+	gsi_writel(gsi, val, GSI_CNTXT_GLOB_IRQ_EN_OFFS);
+	/* Never enable GSI_BREAK_POINT */
+	val &= ~FIELD_PREP(EN_BREAK_POINT_FMASK, 1);
+	gsi_writel(gsi, val, GSI_CNTXT_GSI_IRQ_EN_OFFS);
+}
+
+static void gsi_irq_disable_all(struct gsi *gsi)
+{
+	_gsi_irq_control_all(gsi, false);
+}
+
+static void gsi_irq_enable_all(struct gsi *gsi)
+{
+	_gsi_irq_control_all(gsi, true);
+}
+
+static u32 gsi_channel_id(struct gsi *gsi, struct gsi_channel *channel)
+{
+	return (u32)(channel - &gsi->channel[0]);
+}
+
+static u32 gsi_evt_ring_id(struct gsi *gsi, struct gsi_evt_ring *evt_ring)
+{
+	return (u32)(evt_ring - &gsi->evt_ring[0]);
+}
+
+static enum gsi_channel_state gsi_channel_state(struct gsi *gsi, u32 channel_id)
+{
+	u32 val = gsi_readl(gsi, GSI_CH_C_CNTXT_0_OFFS(channel_id));
+
+	return (enum gsi_channel_state)FIELD_GET(CHSTATE_FMASK, val);
+}
+
+static enum gsi_evt_ring_state
+gsi_evt_ring_state(struct gsi *gsi, u32 evt_ring_id)
+{
+	u32 val = gsi_readl(gsi, GSI_EV_CH_E_CNTXT_0_OFFS(evt_ring_id));
+
+	return (enum gsi_evt_ring_state)FIELD_GET(EV_CHSTATE_FMASK, val);
+}
+
+static void gsi_isr_chan_ctrl(struct gsi *gsi)
+{
+	u32 channel_mask;
+
+	channel_mask = gsi_readl(gsi, GSI_CNTXT_SRC_CH_IRQ_OFFS);
+	gsi_writel(gsi, channel_mask, GSI_CNTXT_SRC_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(channel_mask & ~GENMASK(gsi->channel_max - 1, 0)));
+
+	while (channel_mask) {
+		struct gsi_channel *channel;
+		int i = __ffs(channel_mask);
+
+		channel = &gsi->channel[i];
+		channel->state = gsi_channel_state(gsi, i);
+
+		complete(&channel->compl);
+
+		channel_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_evt_ctrl(struct gsi *gsi)
+{
+	u32 evt_mask;
+
+	evt_mask = gsi_readl(gsi, GSI_CNTXT_SRC_EV_CH_IRQ_OFFS);
+	gsi_writel(gsi, evt_mask, GSI_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(evt_mask & ~GENMASK(gsi->evt_ring_max - 1, 0)));
+
+	while (evt_mask) {
+		struct gsi_evt_ring *evt_ring;
+		int i = __ffs(evt_mask);
+
+		evt_ring = &gsi->evt_ring[i];
+		evt_ring->state = gsi_evt_ring_state(gsi, i);
+
+		complete(&evt_ring->compl);
+
+		evt_mask ^= BIT(i);
+	}
+}
+
+static void
+gsi_isr_glob_chan_err(struct gsi *gsi, u32 err_ee, u32 channel_id, u32 code)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+
+	if (err_ee != IPA_EE_AP)
+		ipa_bug_on(code != GSI_UNSUPPORTED_INTER_EE_OP_ERR);
+
+	if (WARN_ON(channel_id >= gsi->channel_max)) {
+		ipa_err("unexpected channel_id %u\n", channel_id);
+		return;
+	}
+
+	switch (code) {
+	case GSI_INVALID_TRE_ERR:
+		ipa_err("got INVALID_TRE_ERR\n");
+		channel->state = gsi_channel_state(gsi, channel_id);
+		ipa_bug_on(channel->state != GSI_CHANNEL_STATE_ERROR);
+		break;
+	case GSI_OUT_OF_BUFFERS_ERR:
+		ipa_err("got OUT_OF_BUFFERS_ERR\n");
+		break;
+	case GSI_OUT_OF_RESOURCES_ERR:
+		ipa_err("got OUT_OF_RESOURCES_ERR\n");
+		complete(&channel->compl);
+		break;
+	case GSI_UNSUPPORTED_INTER_EE_OP_ERR:
+		ipa_err("got UNSUPPORTED_INTER_EE_OP_ERR\n");
+		break;
+	case GSI_NON_ALLOCATED_EVT_ACCESS_ERR:
+		ipa_err("got NON_ALLOCATED_EVT_ACCESS_ERR\n");
+		break;
+	case GSI_HWO_1_ERR:
+		ipa_err("got HWO_1_ERR\n");
+		break;
+	default:
+		ipa_err("unexpected channel error code %u\n", code);
+		ipa_bug();
+	}
+}
+
+static void
+gsi_isr_glob_evt_err(struct gsi *gsi, u32 err_ee, u32 evt_ring_id, u32 code)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+
+	if (err_ee != IPA_EE_AP)
+		ipa_bug_on(code != GSI_UNSUPPORTED_INTER_EE_OP_ERR);
+
+	if (WARN_ON(evt_ring_id >= gsi->evt_ring_max)) {
+		ipa_err("unexpected evt_ring_id %u\n", evt_ring_id);
+		return;
+	}
+
+	switch (code) {
+	case GSI_OUT_OF_BUFFERS_ERR:
+		ipa_err("got OUT_OF_BUFFERS_ERR\n");
+		break;
+	case GSI_OUT_OF_RESOURCES_ERR:
+		ipa_err("got OUT_OF_RESOURCES_ERR\n");
+		complete(&evt_ring->compl);
+		break;
+	case GSI_UNSUPPORTED_INTER_EE_OP_ERR:
+		ipa_err("got UNSUPPORTED_INTER_EE_OP_ERR\n");
+		break;
+	case GSI_EVT_RING_EMPTY_ERR:
+		ipa_err("got EVT_RING_EMPTY_ERR\n");
+		break;
+	default:
+		ipa_err("unexpected event error code %u\n", code);
+		ipa_bug();
+	}
+}
+
+static void gsi_isr_glob_err(struct gsi *gsi, u32 err)
+{
+	struct gsi_log_err *log = (struct gsi_log_err *)&err;
+
+	ipa_err("log err_type %u ee %u idx %u\n", log->err_type, log->ee,
+		log->virt_idx);
+	ipa_err("log code 0x%1x arg1 0x%1x arg2 0x%1x arg3 0x%1x\n", log->code,
+		log->arg1, log->arg2, log->arg3);
+
+	ipa_bug_on(log->err_type == GSI_ERR_TYPE_GLOB);
+
+	switch (log->err_type) {
+	case GSI_ERR_TYPE_CHAN:
+		gsi_isr_glob_chan_err(gsi, log->ee, log->virt_idx, log->code);
+		break;
+	case GSI_ERR_TYPE_EVT:
+		gsi_isr_glob_evt_err(gsi, log->ee, log->virt_idx, log->code);
+		break;
+	default:
+		WARN_ON(1);
+	}
+}
+
+static void gsi_isr_glob_ee(struct gsi *gsi)
+{
+	u32 val;
+
+	val = gsi_readl(gsi, GSI_CNTXT_GLOB_IRQ_STTS_OFFS);
+
+	if (val & ERROR_INT_FMASK) {
+		u32 err = gsi_readl(gsi, GSI_ERROR_LOG_OFFS);
+
+		gsi_writel(gsi, 0, GSI_ERROR_LOG_OFFS);
+		gsi_writel(gsi, ~0, GSI_ERROR_LOG_CLR_OFFS);
+
+		gsi_isr_glob_err(gsi, err);
+	}
+
+	if (val & EN_GP_INT1_FMASK)
+		ipa_err("unexpected GP INT1 received\n");
+
+	ipa_bug_on(val & EN_GP_INT2_FMASK);
+	ipa_bug_on(val & EN_GP_INT3_FMASK);
+
+	gsi_writel(gsi, val, GSI_CNTXT_GLOB_IRQ_CLR_OFFS);
+}
+
+static void ring_wp_local_inc(struct gsi_ring *ring)
+{
+	ring->wp_local += GSI_RING_ELEMENT_SIZE;
+	if (ring->wp_local == ring->end)
+		ring->wp_local = ring->mem.phys;
+}
+
+static void ring_rp_local_inc(struct gsi_ring *ring)
+{
+	ring->rp_local += GSI_RING_ELEMENT_SIZE;
+	if (ring->rp_local == ring->end)
+		ring->rp_local = ring->mem.phys;
+}
+
+static u16 ring_rp_local_index(struct gsi_ring *ring)
+{
+	return (u16)(ring->rp_local - ring->mem.phys) / GSI_RING_ELEMENT_SIZE;
+}
+
+static u16 ring_wp_local_index(struct gsi_ring *ring)
+{
+	return (u16)(ring->wp_local - ring->mem.phys) / GSI_RING_ELEMENT_SIZE;
+}
+
+static void channel_xfer_cb(struct gsi_channel *channel, u16 count)
+{
+	void *xfer_data;
+
+	if (!channel->from_ipa) {
+		u16 ring_rp_local = ring_rp_local_index(&channel->ring);
+
+		xfer_data = channel->user_data[ring_rp_local];;
+		ipa_gsi_irq_tx_notify_cb(xfer_data);
+	} else {
+		ipa_gsi_irq_rx_notify_cb(channel->notify_data, count);
+	}
+}
+
+static u16 gsi_channel_process(struct gsi *gsi, struct gsi_xfer_compl_evt *evt,
+			       bool callback)
+{
+	struct gsi_channel *channel;
+	u32 channel_id = (u32)evt->chid;
+
+	ipa_assert(channel_id < gsi->channel_max);
+
+	/* Event tells us the last completed channel ring element */
+	channel = &gsi->channel[channel_id];
+	channel->ring.rp_local = evt->xfer_ptr;
+
+	if (callback) {
+		if (evt->code == GSI_CHANNEL_EVT_EOT)
+			channel_xfer_cb(channel, evt->len);
+		else
+			ipa_err("ch %u unexpected %sX event id %hhu\n",
+				channel_id, channel->from_ipa ? "R" : "T",
+				evt->code);
+	}
+
+	/* Record that we've processed this channel ring element. */
+	ring_rp_local_inc(&channel->ring);
+	channel->ring.rp = channel->ring.rp_local;
+
+	return evt->len;
+}
+
+static void
+gsi_evt_ring_doorbell(struct gsi *gsi, struct gsi_evt_ring *evt_ring)
+{
+	u32 evt_ring_id = gsi_evt_ring_id(gsi, evt_ring);
+	u32 val;
+
+	/* The doorbell 0 and 1 registers store the low-order and
+	 * high-order 32 bits of the event ring doorbell register,
+	 * respectively.  LSB (doorbell 0) must be written last.
+	 */
+	val = evt_ring->ring.wp_local >> 32;
+	gsi_writel(gsi, val, GSI_EV_CH_E_DOORBELL_1_OFFS(evt_ring_id));
+
+	val = evt_ring->ring.wp_local & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_EV_CH_E_DOORBELL_0_OFFS(evt_ring_id));
+}
+
+static void gsi_channel_doorbell(struct gsi *gsi, struct gsi_channel *channel)
+{
+	u32 channel_id = gsi_channel_id(gsi, channel);
+	u32 val;
+
+	/* allocate new events for this channel first
+	 * before submitting the new TREs.
+	 * for TO_GSI channels the event ring doorbell is rang as part of
+	 * interrupt handling.
+	 */
+	if (channel->from_ipa)
+		gsi_evt_ring_doorbell(gsi, channel->evt_ring);
+	channel->ring.wp = channel->ring.wp_local;
+
+	/* The doorbell 0 and 1 registers store the low-order and
+	 * high-order 32 bits of the channel ring doorbell register,
+	 * respectively.  LSB (doorbell 0) must be written last.
+	 */
+	val = channel->ring.wp_local >> 32;
+	gsi_writel(gsi, val, GSI_CH_C_DOORBELL_1_OFFS(channel_id));
+	val = channel->ring.wp_local & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_CH_C_DOORBELL_0_OFFS(channel_id));
+}
+
+static void gsi_event_handle(struct gsi *gsi, u32 evt_ring_id)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+	unsigned long flags;
+	bool check_again;
+
+	spin_lock_irqsave(&evt_ring->ring.slock, flags);
+
+	do {
+		u32 val = gsi_readl(gsi, GSI_EV_CH_E_CNTXT_4_OFFS(evt_ring_id));
+
+		evt_ring->ring.rp = evt_ring->ring.rp & GENMASK_ULL(63, 32);
+		evt_ring->ring.rp |= val;
+
+		check_again = false;
+		while (evt_ring->ring.rp_local != evt_ring->ring.rp) {
+			struct gsi_xfer_compl_evt *evt;
+
+			if (atomic_read(&evt_ring->channel->poll_mode)) {
+				check_again = false;
+				break;
+			}
+			check_again = true;
+
+			evt = ipa_dma_phys_to_virt(&evt_ring->ring.mem,
+						   evt_ring->ring.rp_local);
+			(void)gsi_channel_process(gsi, evt, true);
+
+			ring_rp_local_inc(&evt_ring->ring);
+			ring_wp_local_inc(&evt_ring->ring); /* recycle */
+		}
+
+		gsi_evt_ring_doorbell(gsi, evt_ring);
+	} while (check_again);
+
+	spin_unlock_irqrestore(&evt_ring->ring.slock, flags);
+}
+
+static void gsi_isr_ioeb(struct gsi *gsi)
+{
+	u32 evt_mask;
+
+	evt_mask = gsi_readl(gsi, GSI_CNTXT_SRC_IEOB_IRQ_OFFS);
+	evt_mask &= gsi_readl(gsi, GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS);
+	gsi_writel(gsi, evt_mask, GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFS);
+
+	ipa_assert(!(evt_mask & ~GENMASK(gsi->evt_ring_max - 1, 0)));
+
+	while (evt_mask) {
+		u32 i = (u32)__ffs(evt_mask);
+
+		gsi_event_handle(gsi, i);
+
+		evt_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_inter_ee_chan_ctrl(struct gsi *gsi)
+{
+	u32 channel_mask;
+
+	channel_mask = gsi_readl(gsi, GSI_INTER_EE_SRC_CH_IRQ_OFFS);
+	gsi_writel(gsi, channel_mask, GSI_INTER_EE_SRC_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(channel_mask & ~GENMASK(gsi->channel_max - 1, 0)));
+
+	while (channel_mask) {
+		int i = __ffs(channel_mask);
+
+		/* not currently expected */
+		ipa_err("ch %d was inter-EE changed\n", i);
+		channel_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_inter_ee_evt_ctrl(struct gsi *gsi)
+{
+	u32 evt_mask;
+
+	evt_mask = gsi_readl(gsi, GSI_INTER_EE_SRC_EV_CH_IRQ_OFFS);
+	gsi_writel(gsi, evt_mask, GSI_INTER_EE_SRC_EV_CH_IRQ_CLR_OFFS);
+
+	ipa_assert(!(evt_mask & ~GENMASK(gsi->evt_ring_max - 1, 0)));
+
+	while (evt_mask) {
+		u32 i = (u32)__ffs(evt_mask);
+
+		/* not currently expected */
+		ipa_err("evt %d was inter-EE changed\n", i);
+		evt_mask ^= BIT(i);
+	}
+}
+
+static void gsi_isr_general(struct gsi *gsi)
+{
+	u32 val;
+
+	val = gsi_readl(gsi, GSI_CNTXT_GSI_IRQ_STTS_OFFS);
+
+	ipa_bug_on(val & CLR_MCS_STACK_OVRFLOW_FMASK);
+	ipa_bug_on(val & CLR_CMD_FIFO_OVRFLOW_FMASK);
+	ipa_bug_on(val & CLR_BUS_ERROR_FMASK);
+
+	if (val & CLR_BREAK_POINT_FMASK)
+		ipa_err("got breakpoint\n");
+
+	gsi_writel(gsi, val, GSI_CNTXT_GSI_IRQ_CLR_OFFS);
+}
+
+/* Returns a bitmask of pending GSI interrupts */
+static u32 gsi_isr_type(struct gsi *gsi)
+{
+	return gsi_readl(gsi, GSI_CNTXT_TYPE_IRQ_OFFS);
+}
+
+static irqreturn_t gsi_isr(int irq, void *dev_id)
+{
+	struct gsi *gsi = dev_id;
+	u32 type;
+	u32 cnt;
+
+	cnt = 0;
+	while ((type = gsi_isr_type(gsi))) {
+		do {
+			u32 single = BIT(__ffs(type));
+
+			switch (single) {
+			case CH_CTRL_FMASK:
+				gsi_isr_chan_ctrl(gsi);
+				break;
+			case EV_CTRL_FMASK:
+				gsi_isr_evt_ctrl(gsi);
+				break;
+			case GLOB_EE_FMASK:
+				gsi_isr_glob_ee(gsi);
+				break;
+			case IEOB_FMASK:
+				gsi_isr_ioeb(gsi);
+				break;
+			case INTER_EE_CH_CTRL_FMASK:
+				gsi_isr_inter_ee_chan_ctrl(gsi);
+				break;
+			case INTER_EE_EV_CTRL_FMASK:
+				gsi_isr_inter_ee_evt_ctrl(gsi);
+				break;
+			case GENERAL_FMASK:
+				gsi_isr_general(gsi);
+				break;
+			default:
+				WARN(true, "%s: unrecognized type 0x%08x\n",
+				     __func__, single);
+				break;
+			}
+			type ^= single;
+		} while (type);
+
+		ipa_bug_on(++cnt > GSI_ISR_MAX_ITER);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static u32 gsi_channel_max(struct gsi *gsi)
+{
+	u32 val = gsi_readl(gsi, GSI_GSI_HW_PARAM_2_OFFS);
+
+	return FIELD_GET(NUM_CH_PER_EE_FMASK, val);
+}
+
+static u32 gsi_evt_ring_max(struct gsi *gsi)
+{
+	u32 val = gsi_readl(gsi, GSI_GSI_HW_PARAM_2_OFFS);
+
+	return FIELD_GET(NUM_EV_PER_EE_FMASK, val);
+}
+
+/* Zero bits in an event bitmap represent event numbers available
+ * for allocation.  Initialize the map so all events supported by
+ * the hardware are available; then preclude any reserved events
+ * from allocation.
+ */
+static u32 gsi_evt_bmap_init(u32 evt_ring_max)
+{
+	u32 evt_bmap = GENMASK(BITS_PER_LONG - 1, evt_ring_max);
+
+	return evt_bmap | GENMASK(GSI_MHI_ER_END, GSI_MHI_ER_START);
+}
+
+int gsi_device_init(struct gsi *gsi)
+{
+	u32 evt_ring_max;
+	u32 channel_max;
+	u32 val;
+	int ret;
+
+	val = gsi_readl(gsi, GSI_GSI_STATUS_OFFS);
+	if (!(val & ENABLED_FMASK)) {
+		ipa_err("manager EE has not enabled GSI, GSI un-usable\n");
+		return -EIO;
+	}
+
+	channel_max = gsi_channel_max(gsi);
+	ipa_debug("channel_max %u\n", channel_max);
+	ipa_assert(channel_max <= GSI_CHAN_MAX);
+
+	evt_ring_max = gsi_evt_ring_max(gsi);
+	ipa_debug("evt_ring_max %u\n", evt_ring_max);
+	ipa_assert(evt_ring_max <= GSI_EVT_RING_MAX);
+
+	ret = request_irq(gsi->irq, gsi_isr, IRQF_TRIGGER_HIGH, "gsi", gsi);
+	if (ret) {
+		ipa_err("failed to register isr for %u\n", gsi->irq);
+		return -EIO;
+	}
+
+	ret = enable_irq_wake(gsi->irq);
+	if (ret)
+		ipa_err("error %d enabling gsi wake irq\n", ret);
+	gsi->irq_wake_enabled = !ret;
+	gsi->channel_max = channel_max;
+	gsi->evt_ring_max = evt_ring_max;
+	gsi->evt_bmap = gsi_evt_bmap_init(evt_ring_max);
+
+	/* Enable all IPA interrupts */
+	gsi_irq_enable_all(gsi);
+
+	/* Writing 1 indicates IRQ interrupts; 0 would be MSI */
+	gsi_writel(gsi, 1, GSI_CNTXT_INTSET_OFFS);
+
+	/* Initialize the error log */
+	gsi_writel(gsi, 0, GSI_ERROR_LOG_OFFS);
+
+	return 0;
+}
+
+void gsi_device_exit(struct gsi *gsi)
+{
+	ipa_assert(!atomic_read(&gsi->channel_count));
+	ipa_assert(!atomic_read(&gsi->evt_ring_count));
+
+	/* Don't bother clearing the error log again (ERROR_LOG) or
+	 * setting the interrupt type again (INTSET).
+	 */
+	gsi_irq_disable_all(gsi);
+
+	/* Clean up everything else set up by gsi_device_init() */
+	gsi->evt_bmap = 0;
+	gsi->evt_ring_max = 0;
+	gsi->channel_max = 0;
+	if (gsi->irq_wake_enabled) {
+		(void)disable_irq_wake(gsi->irq);
+		gsi->irq_wake_enabled = false;
+	}
+	free_irq(gsi->irq, gsi);
+	gsi->irq = 0;
+}
+
+static void gsi_evt_ring_program(struct gsi *gsi, u32 evt_ring_id)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+	u32 int_modt;
+	u32 int_modc;
+	u64 phys;
+	u32 val;
+
+	phys = evt_ring->ring.mem.phys;
+	int_modt = evt_ring->moderation ? IPA_GSI_EVT_RING_INT_MODT : 0;
+	int_modc = 1;	/* moderation always comes from channel*/
+
+	val = FIELD_PREP(EV_CHTYPE_FMASK, GSI_EVT_CHTYPE_GPI_EV);
+	val |= FIELD_PREP(EV_INTYPE_FMASK, 1);
+	val |= FIELD_PREP(EV_ELEMENT_SIZE_FMASK, GSI_RING_ELEMENT_SIZE);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_0_OFFS(evt_ring_id));
+
+	val = FIELD_PREP(EV_R_LENGTH_FMASK, (u32)evt_ring->ring.mem.size);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_1_OFFS(evt_ring_id));
+
+	/* The context 2 and 3 registers store the low-order and
+	 * high-order 32 bits of the address of the event ring,
+	 * respectively.
+	 */
+	val = phys & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_2_OFFS(evt_ring_id));
+
+	val = phys >> 32;
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_3_OFFS(evt_ring_id));
+
+	val = FIELD_PREP(MODT_FMASK, int_modt);
+	val |= FIELD_PREP(MODC_FMASK, int_modc);
+	gsi_writel(gsi, val, GSI_EV_CH_E_CNTXT_8_OFFS(evt_ring_id));
+
+	/* No MSI write data, and MSI address high and low address is 0 */
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_9_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_10_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_11_OFFS(evt_ring_id));
+
+	/* We don't need to get event read pointer updates */
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_12_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_CNTXT_13_OFFS(evt_ring_id));
+}
+
+static void gsi_ring_init(struct gsi_ring *ring)
+{
+	ring->wp_local = ring->wp = ring->mem.phys;
+	ring->rp_local = ring->rp = ring->mem.phys;
+}
+
+static int gsi_ring_alloc(struct gsi_ring *ring, u32 count)
+{
+	size_t size = roundup_pow_of_two(count * GSI_RING_ELEMENT_SIZE);
+
+	/* Hardware requires a power-of-2 ring size (and alignment) */
+	if (ipa_dma_alloc(&ring->mem, size, GFP_KERNEL))
+		return -ENOMEM;
+	ipa_assert(!(ring->mem.phys % size));
+
+	ring->end = ring->mem.phys + size;
+	spin_lock_init(&ring->slock);
+
+	return 0;
+}
+
+static void gsi_ring_free(struct gsi_ring *ring)
+{
+	ipa_dma_free(&ring->mem);
+	memset(ring, 0, sizeof(*ring));
+}
+
+static void gsi_evt_ring_prime(struct gsi *gsi, struct gsi_evt_ring *evt_ring)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&evt_ring->ring.slock, flags);
+	memset(evt_ring->ring.mem.virt, 0, evt_ring->ring.mem.size);
+	evt_ring->ring.wp_local = evt_ring->ring.end - GSI_RING_ELEMENT_SIZE;
+	gsi_evt_ring_doorbell(gsi, evt_ring);
+	spin_unlock_irqrestore(&evt_ring->ring.slock, flags);
+}
+
+/* Issue a GSI command by writing a value to a register, then wait
+ * for completion to be signaled.  Returns true if successful or
+ * false if a timeout occurred.  Note that the register offset is
+ * first, value to write is second (reverse of writel() order).
+ */
+static bool command(struct gsi *gsi, u32 reg, u32 val, struct completion *compl)
+{
+	bool ret;
+
+	gsi_writel(gsi, val, reg);
+	ret = !!wait_for_completion_timeout(compl, GSI_CMD_TIMEOUT);
+	if (!ret)
+		ipa_err("command timeout\n");
+
+	return ret;
+}
+
+/* Issue an event ring command and wait for it to complete */
+static bool evt_ring_command(struct gsi *gsi, u32 evt_ring_id,
+			     enum gsi_evt_ch_cmd_opcode op)
+{
+	struct completion *compl = &gsi->evt_ring[evt_ring_id].compl;
+	u32 val;
+
+	reinit_completion(compl);
+
+	val = FIELD_PREP(EV_CHID_FMASK, evt_ring_id);
+	val |= FIELD_PREP(EV_OPCODE_FMASK, (u32)op);
+
+	return command(gsi, GSI_EV_CH_CMD_OFFS, val, compl);
+}
+
+/* Issue a channel command and wait for it to complete */
+static bool
+channel_command(struct gsi *gsi, u32 channel_id, enum gsi_ch_cmd_opcode op)
+{
+	struct completion *compl = &gsi->channel[channel_id].compl;
+	u32 val;
+
+	reinit_completion(compl);
+
+	val = FIELD_PREP(CH_CHID_FMASK, channel_id);
+	val |= FIELD_PREP(CH_OPCODE_FMASK, (u32)op);
+
+	return command(gsi, GSI_CH_CMD_OFFS, val, compl);
+}
+
+/* Note: only GPI interfaces, IRQ interrupts are currently supported */
+static int gsi_evt_ring_alloc(struct gsi *gsi, u32 ring_count, bool moderation)
+{
+	struct gsi_evt_ring *evt_ring;
+	unsigned long flags;
+	u32 evt_ring_id;
+	u32 val;
+	int ret;
+
+	/* Get the mutex to allocate from the bitmap and issue a command */
+	mutex_lock(&gsi->mutex);
+
+	/* Start by allocating the event id to use */
+	ipa_assert(gsi->evt_bmap != ~0UL);
+	evt_ring_id = (u32)ffz(gsi->evt_bmap);
+	gsi->evt_bmap |= BIT(evt_ring_id);
+
+	evt_ring = &gsi->evt_ring[evt_ring_id];
+
+	ret = gsi_ring_alloc(&evt_ring->ring, ring_count);
+	if (ret)
+		goto err_free_bmap;
+
+	init_completion(&evt_ring->compl);
+
+	if (!evt_ring_command(gsi, evt_ring_id, GSI_EVT_ALLOCATE)) {
+		ret = -ETIMEDOUT;
+		goto err_free_ring;
+	}
+
+	if (evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED) {
+		ipa_err("evt_ring_id %u allocation failed state %u\n",
+			evt_ring_id, evt_ring->state);
+		ret = -ENOMEM;
+		goto err_free_ring;
+	}
+	atomic_inc(&gsi->evt_ring_count);
+
+	evt_ring->moderation = moderation;
+
+	gsi_evt_ring_program(gsi, evt_ring_id);
+	gsi_ring_init(&evt_ring->ring);
+	gsi_evt_ring_prime(gsi, evt_ring);
+
+	mutex_unlock(&gsi->mutex);
+
+	spin_lock_irqsave(&gsi->slock, flags);
+
+	/* Enable the event interrupt (clear it first in case pending) */
+	val = BIT(evt_ring_id);
+	gsi_writel(gsi, val, GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFS);
+	gsi_irq_enable_event(gsi, evt_ring_id);
+
+	spin_unlock_irqrestore(&gsi->slock, flags);
+
+	return evt_ring_id;
+
+err_free_ring:
+	gsi_ring_free(&evt_ring->ring);
+	memset(evt_ring, 0, sizeof(*evt_ring));
+err_free_bmap:
+	ipa_assert(gsi->evt_bmap & BIT(evt_ring_id));
+	gsi->evt_bmap &= ~BIT(evt_ring_id);
+
+	mutex_unlock(&gsi->mutex);
+
+	return ret;
+}
+
+static void gsi_evt_ring_scratch_zero(struct gsi *gsi, u32 evt_ring_id)
+{
+	gsi_writel(gsi, 0, GSI_EV_CH_E_SCRATCH_0_OFFS(evt_ring_id));
+	gsi_writel(gsi, 0, GSI_EV_CH_E_SCRATCH_1_OFFS(evt_ring_id));
+}
+
+static void gsi_evt_ring_dealloc(struct gsi *gsi, u32 evt_ring_id)
+{
+	struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id];
+	bool completed;
+
+	ipa_bug_on(evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED);
+
+	mutex_lock(&gsi->mutex);
+
+	completed = evt_ring_command(gsi, evt_ring_id, GSI_EVT_RESET);
+	ipa_bug_on(!completed);
+	ipa_bug_on(evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED);
+
+	gsi_evt_ring_program(gsi, evt_ring_id);
+	gsi_ring_init(&evt_ring->ring);
+	gsi_evt_ring_scratch_zero(gsi, evt_ring_id);
+	gsi_evt_ring_prime(gsi, evt_ring);
+
+	completed = evt_ring_command(gsi, evt_ring_id, GSI_EVT_DE_ALLOC);
+	ipa_bug_on(!completed);
+
+	ipa_bug_on(evt_ring->state != GSI_EVT_RING_STATE_NOT_ALLOCATED);
+
+	ipa_assert(gsi->evt_bmap & BIT(evt_ring_id));
+	gsi->evt_bmap &= ~BIT(evt_ring_id);
+
+	mutex_unlock(&gsi->mutex);
+
+	evt_ring->moderation = false;
+	gsi_ring_free(&evt_ring->ring);
+	memset(evt_ring, 0, sizeof(*evt_ring));
+
+	atomic_dec(&gsi->evt_ring_count);
+}
+
+static void gsi_channel_program(struct gsi *gsi, u32 channel_id,
+				u32 evt_ring_id, bool doorbell_enable)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 low_weight;
+	u32 val;
+
+	val = FIELD_PREP(CHTYPE_PROTOCOL_FMASK, GSI_CHANNEL_PROTOCOL_GPI);
+	val |= FIELD_PREP(CHTYPE_DIR_FMASK, channel->from_ipa ? 0 : 1);
+	val |= FIELD_PREP(ERINDEX_FMASK, evt_ring_id);
+	val |= FIELD_PREP(ELEMENT_SIZE_FMASK, GSI_RING_ELEMENT_SIZE);
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_0_OFFS(channel_id));
+
+	val = FIELD_PREP(R_LENGTH_FMASK, channel->ring.mem.size);
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_1_OFFS(channel_id));
+
+	/* The context 2 and 3 registers store the low-order and
+	 * high-order 32 bits of the address of the channel ring,
+	 * respectively.
+	 */
+	val = channel->ring.mem.phys & GENMASK(31, 0);
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_2_OFFS(channel_id));
+
+	val = channel->ring.mem.phys >> 32;
+	gsi_writel(gsi, val, GSI_CH_C_CNTXT_3_OFFS(channel_id));
+
+	low_weight = channel->priority ? FIELD_MAX(WRR_WEIGHT_FMASK) : 0;
+	val = FIELD_PREP(WRR_WEIGHT_FMASK, low_weight);
+	val |= FIELD_PREP(MAX_PREFETCH_FMASK, GSI_MAX_PREFETCH);
+	val |= FIELD_PREP(USE_DB_ENG_FMASK, doorbell_enable ? 1 : 0);
+	gsi_writel(gsi, val, GSI_CH_C_QOS_OFFS(channel_id));
+}
+
+int gsi_channel_alloc(struct gsi *gsi, u32 channel_id, u32 channel_count,
+		      bool from_ipa, bool priority, u32 evt_ring_mult,
+		      bool moderation, void *notify_data)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_count;
+	u32 evt_ring_id;
+	void **user_data;
+	int ret;
+
+	evt_ring_count = channel_count * evt_ring_mult;
+	ret = gsi_evt_ring_alloc(gsi, evt_ring_count, moderation);
+	if (ret < 0)
+		return ret;
+	evt_ring_id = (u32)ret;
+
+	ret = gsi_ring_alloc(&channel->ring, channel_count);
+	if (ret)
+		goto err_evt_ring_free;
+
+	user_data = kcalloc(channel_count, sizeof(void *), GFP_KERNEL);
+	if (!user_data) {
+		ret = -ENOMEM;
+		goto err_ring_free;
+	}
+
+	mutex_init(&channel->mutex);
+	init_completion(&channel->compl);
+	atomic_set(&channel->poll_mode, 0);	/* Initially in callback mode */
+	channel->from_ipa = from_ipa;
+	channel->notify_data = notify_data;
+
+	mutex_lock(&gsi->mutex);
+
+	if (!channel_command(gsi, channel_id, GSI_CH_ALLOCATE)) {
+		ret = -ETIMEDOUT;
+		goto err_mutex_unlock;
+	}
+	if (channel->state != GSI_CHANNEL_STATE_ALLOCATED) {
+		ret = -EIO;
+		goto err_mutex_unlock;
+	}
+
+	gsi->ch_dbg[channel_id].ch_allocate++;
+
+	mutex_unlock(&gsi->mutex);
+
+	channel->evt_ring = &gsi->evt_ring[evt_ring_id];
+	channel->evt_ring->channel = channel;
+	channel->priority = priority;
+
+	gsi_channel_program(gsi, channel_id, evt_ring_id, true);
+	gsi_ring_init(&channel->ring);
+
+	channel->user_data = user_data;
+	atomic_inc(&gsi->channel_count);
+
+	return 0;
+
+err_mutex_unlock:
+	mutex_unlock(&gsi->mutex);
+	kfree(user_data);
+err_ring_free:
+	gsi_ring_free(&channel->ring);
+err_evt_ring_free:
+	gsi_evt_ring_dealloc(gsi, evt_ring_id);
+
+	return ret;
+}
+
+static void __gsi_channel_scratch_write(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	struct gsi_gpi_channel_scratch *gpi;
+	union gsi_channel_scratch scr = { };
+	u32 val;
+
+	gpi = &scr.gpi;
+	/* See comments above definition of gsi_gpi_channel_scratch */
+	gpi->max_outstanding_tre = channel->tlv_count * GSI_RING_ELEMENT_SIZE;
+	gpi->outstanding_threshold = 2 * GSI_RING_ELEMENT_SIZE;
+
+	val = scr.data.word1;
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_0_OFFS(channel_id));
+
+	val = scr.data.word2;
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_1_OFFS(channel_id));
+
+	val = scr.data.word3;
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_2_OFFS(channel_id));
+
+	/* We must preserve the upper 16 bits of the last scratch
+	 * register.  The next sequence assumes those bits remain
+	 * unchanged between the read and the write.
+	 */
+	val = gsi_readl(gsi, GSI_CH_C_SCRATCH_3_OFFS(channel_id));
+	val = (scr.data.word4 & GENMASK(31, 16)) | (val & GENMASK(15, 0));
+	gsi_writel(gsi, val, GSI_CH_C_SCRATCH_3_OFFS(channel_id));
+}
+
+void gsi_channel_scratch_write(struct gsi *gsi, u32 channel_id, u32 tlv_count)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+
+	channel->tlv_count = tlv_count;
+
+	mutex_lock(&channel->mutex);
+
+	__gsi_channel_scratch_write(gsi, channel_id);
+
+	mutex_unlock(&channel->mutex);
+}
+
+int gsi_channel_start(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+
+	if (channel->state != GSI_CHANNEL_STATE_ALLOCATED &&
+	    channel->state != GSI_CHANNEL_STATE_STOP_IN_PROC &&
+	    channel->state != GSI_CHANNEL_STATE_STOPPED) {
+		ipa_err("bad state %d\n", channel->state);
+		return -ENOTSUPP;
+	}
+
+	mutex_lock(&gsi->mutex);
+
+	gsi->ch_dbg[channel_id].ch_start++;
+
+	if (!channel_command(gsi, channel_id, GSI_CH_START)) {
+		mutex_unlock(&gsi->mutex);
+		return -ETIMEDOUT;
+	}
+	if (channel->state != GSI_CHANNEL_STATE_STARTED) {
+		ipa_err("channel %u unexpected state %u\n", channel_id,
+			channel->state);
+		ipa_bug();
+	}
+
+	mutex_unlock(&gsi->mutex);
+
+	return 0;
+}
+
+int gsi_channel_stop(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	int ret;
+
+	if (channel->state == GSI_CHANNEL_STATE_STOPPED)
+		return 0;
+
+	if (channel->state != GSI_CHANNEL_STATE_STARTED &&
+	    channel->state != GSI_CHANNEL_STATE_STOP_IN_PROC &&
+	    channel->state != GSI_CHANNEL_STATE_ERROR) {
+		ipa_err("bad state %d\n", channel->state);
+		return -ENOTSUPP;
+	}
+
+	mutex_lock(&gsi->mutex);
+
+	gsi->ch_dbg[channel_id].ch_stop++;
+
+	if (!channel_command(gsi, channel_id, GSI_CH_STOP)) {
+		/* check channel state here in case the channel is stopped but
+		 * the interrupt was not handled yet.
+		 */
+		channel->state = gsi_channel_state(gsi, channel_id);
+		if (channel->state == GSI_CHANNEL_STATE_STOPPED) {
+			ret = 0;
+			goto free_lock;
+		}
+		ret = -ETIMEDOUT;
+		goto free_lock;
+	}
+
+	if (channel->state != GSI_CHANNEL_STATE_STOPPED &&
+	    channel->state != GSI_CHANNEL_STATE_STOP_IN_PROC) {
+		ipa_err("channel %u unexpected state %u\n", channel_id,
+			channel->state);
+		ret = -EBUSY;
+		goto free_lock;
+	}
+
+	if (channel->state == GSI_CHANNEL_STATE_STOP_IN_PROC) {
+		ipa_err("channel %u busy try again\n", channel_id);
+		ret = -EAGAIN;
+		goto free_lock;
+	}
+
+	ret = 0;
+
+free_lock:
+	mutex_unlock(&gsi->mutex);
+
+	return ret;
+}
+
+int gsi_channel_reset(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_id;
+	bool reset_done;
+
+	if (channel->state != GSI_CHANNEL_STATE_STOPPED) {
+		ipa_err("bad state %d\n", channel->state);
+		return -ENOTSUPP;
+	}
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+	reset_done = false;
+	mutex_lock(&gsi->mutex);
+reset:
+
+	gsi->ch_dbg[channel_id].ch_reset++;
+
+	if (!channel_command(gsi, channel_id, GSI_CH_RESET)) {
+		mutex_unlock(&gsi->mutex);
+		return -ETIMEDOUT;
+	}
+
+	if (channel->state != GSI_CHANNEL_STATE_ALLOCATED) {
+		ipa_err("channel_id %u unexpected state %u\n", channel_id,
+			channel->state);
+		ipa_bug();
+	}
+
+	/* workaround: reset GSI producers again */
+	if (channel->from_ipa && !reset_done) {
+		usleep_range(GSI_RESET_WA_MIN_SLEEP, GSI_RESET_WA_MAX_SLEEP);
+		reset_done = true;
+		goto reset;
+	}
+
+	gsi_channel_program(gsi, channel_id, evt_ring_id, true);
+	gsi_ring_init(&channel->ring);
+
+	/* restore scratch */
+	__gsi_channel_scratch_write(gsi, channel_id);
+
+	mutex_unlock(&gsi->mutex);
+
+	return 0;
+}
+
+void gsi_channel_free(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_id;
+	bool completed;
+
+	ipa_bug_on(channel->state != GSI_CHANNEL_STATE_ALLOCATED);
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+	mutex_lock(&gsi->mutex);
+
+	gsi->ch_dbg[channel_id].ch_de_alloc++;
+
+	completed = channel_command(gsi, channel_id, GSI_CH_DE_ALLOC);
+	ipa_bug_on(!completed);
+
+	ipa_bug_on(channel->state != GSI_CHANNEL_STATE_NOT_ALLOCATED);
+
+	mutex_unlock(&gsi->mutex);
+
+	kfree(channel->user_data);
+	gsi_ring_free(&channel->ring);
+
+	gsi_evt_ring_dealloc(gsi, evt_ring_id);
+
+	memset(channel, 0, sizeof(*channel));
+
+	atomic_dec(&gsi->channel_count);
+}
+
+static u16 __gsi_query_ring_free_re(struct gsi_ring *ring)
+{
+	u64 delta;
+
+	if (ring->wp_local < ring->rp_local)
+		delta = ring->rp_local - ring->wp_local;
+	else
+		delta = ring->end - ring->wp_local + ring->rp_local;
+
+	return (u16)(delta / GSI_RING_ELEMENT_SIZE - 1);
+}
+
+int gsi_channel_queue(struct gsi *gsi, u32 channel_id, u16 num_xfers,
+		      struct gsi_xfer_elem *xfer, bool ring_db)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	unsigned long flags;
+	u32 i;
+
+	spin_lock_irqsave(&channel->evt_ring->ring.slock, flags);
+
+	if (num_xfers > __gsi_query_ring_free_re(&channel->ring)) {
+		spin_unlock_irqrestore(&channel->evt_ring->ring.slock, flags);
+		ipa_err("no space for %u-element transfer on ch %u\n",
+			num_xfers, channel_id);
+
+		return -ENOSPC;
+	}
+
+	for (i = 0; i < num_xfers; i++) {
+		struct gsi_tre *tre_ptr;
+		u16 idx = ring_wp_local_index(&channel->ring);
+
+		channel->user_data[idx] = xfer[i].user_data;
+
+		tre_ptr = ipa_dma_phys_to_virt(&channel->ring.mem,
+						  channel->ring.wp_local);
+
+		tre_ptr->buffer_ptr = xfer[i].addr;
+		tre_ptr->buf_len = xfer[i].len_opcode;
+		tre_ptr->bei = xfer[i].flags & GSI_XFER_FLAG_BEI ? 1 : 0;
+		tre_ptr->ieot = xfer[i].flags & GSI_XFER_FLAG_EOT ? 1 : 0;
+		tre_ptr->ieob = xfer[i].flags & GSI_XFER_FLAG_EOB ? 1 : 0;
+		tre_ptr->chain = xfer[i].flags & GSI_XFER_FLAG_CHAIN ? 1 : 0;
+
+		if (xfer[i].type == GSI_XFER_ELEM_DATA)
+			tre_ptr->re_type = GSI_RE_XFER;
+		else if (xfer[i].type == GSI_XFER_ELEM_IMME_CMD)
+			tre_ptr->re_type = GSI_RE_IMMD_CMD;
+		else if (xfer[i].type == GSI_XFER_ELEM_NOP)
+			tre_ptr->re_type = GSI_RE_NOP;
+		else
+			ipa_bug_on("invalid xfer type");
+
+		ring_wp_local_inc(&channel->ring);
+	}
+
+	wmb();	/* Ensure TRE is set before ringing doorbell */
+
+	if (ring_db)
+		gsi_channel_doorbell(gsi, channel);
+
+	spin_unlock_irqrestore(&channel->evt_ring->ring.slock, flags);
+
+	return 0;
+}
+
+int gsi_channel_poll(struct gsi *gsi, u32 channel_id)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	struct gsi_evt_ring *evt_ring;
+	unsigned long flags;
+	u32 evt_ring_id;
+	int size;
+
+	evt_ring = channel->evt_ring;
+	evt_ring_id = gsi_evt_ring_id(gsi, evt_ring);
+
+	spin_lock_irqsave(&evt_ring->ring.slock, flags);
+
+	/* update rp to see of we have anything new to process */
+	if (evt_ring->ring.rp == evt_ring->ring.rp_local) {
+		u32 val;
+
+		val = gsi_readl(gsi, GSI_EV_CH_E_CNTXT_4_OFFS(evt_ring_id));
+		evt_ring->ring.rp = channel->ring.rp & GENMASK_ULL(63, 32);
+		evt_ring->ring.rp |= val;
+	}
+
+	if (evt_ring->ring.rp != evt_ring->ring.rp_local) {
+		struct gsi_xfer_compl_evt *evt;
+
+		evt = ipa_dma_phys_to_virt(&evt_ring->ring.mem,
+					   evt_ring->ring.rp_local);
+		size = gsi_channel_process(gsi, evt, false);
+
+		ring_rp_local_inc(&evt_ring->ring);
+		ring_wp_local_inc(&evt_ring->ring); /* recycle element */
+	} else {
+		size = -ENOENT;
+	}
+
+	spin_unlock_irqrestore(&evt_ring->ring.slock, flags);
+
+	return size;
+}
+
+static void gsi_channel_mode_set(struct gsi *gsi, u32 channel_id, bool polling)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	unsigned long flags;
+	u32 evt_ring_id;
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+
+	spin_lock_irqsave(&gsi->slock, flags);
+
+	if (polling)
+		gsi_irq_disable_event(gsi, evt_ring_id);
+	else
+		gsi_irq_enable_event(gsi, evt_ring_id);
+	atomic_set(&channel->poll_mode, polling ? 1 : 0);
+
+	spin_unlock_irqrestore(&gsi->slock, flags);
+}
+
+void gsi_channel_intr_enable(struct gsi *gsi, u32 channel_id)
+{
+	gsi_channel_mode_set(gsi, channel_id, false);
+}
+
+void gsi_channel_intr_disable(struct gsi *gsi, u32 channel_id)
+{
+	gsi_channel_mode_set(gsi, channel_id, true);
+}
+
+void gsi_channel_config(struct gsi *gsi, u32 channel_id, bool doorbell_enable)
+{
+	struct gsi_channel *channel = &gsi->channel[channel_id];
+	u32 evt_ring_id;
+
+	evt_ring_id = gsi_evt_ring_id(gsi, channel->evt_ring);
+
+	mutex_lock(&channel->mutex);
+
+	gsi_channel_program(gsi, channel_id, evt_ring_id, doorbell_enable);
+	gsi_ring_init(&channel->ring);
+
+	/* restore scratch */
+	__gsi_channel_scratch_write(gsi, channel_id);
+	mutex_unlock(&channel->mutex);
+}
+
+/* Initialize GSI driver */
+struct gsi *gsi_init(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	resource_size_t size;
+	struct gsi *gsi;
+	int irq;
+
+	/* Get GSI memory range and map it */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gsi");
+	if (!res) {
+		ipa_err("missing \"gsi\" property in DTB\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	size = resource_size(res);
+	if (res->start > U32_MAX || size > U32_MAX) {
+		ipa_err("\"gsi\" values out of range\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	/* Get IPA GSI IRQ number */
+	irq = platform_get_irq_byname(pdev, "gsi");
+	if (irq < 0) {
+		ipa_err("failed to get gsi IRQ!\n");
+		return ERR_PTR(irq);
+	}
+
+	gsi = kzalloc(sizeof(*gsi), GFP_KERNEL);
+	if (!gsi)
+		return ERR_PTR(-ENOMEM);
+
+	gsi->base = devm_ioremap_nocache(dev, res->start, size);
+	if (!gsi->base) {
+		kfree(gsi);
+
+		return ERR_PTR(-ENOMEM);
+	}
+	gsi->dev = dev;
+	gsi->phys = (u32)res->start;
+	gsi->irq = irq;
+	spin_lock_init(&gsi->slock);
+	mutex_init(&gsi->mutex);
+	atomic_set(&gsi->channel_count, 0);
+	atomic_set(&gsi->evt_ring_count, 0);
+
+	return gsi;
+}
diff --git a/drivers/net/ipa/gsi.h b/drivers/net/ipa/gsi.h
new file mode 100644
index 000000000000..497f67cc6f80
--- /dev/null
+++ b/drivers/net/ipa/gsi.h
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _GSI_H_
+#define _GSI_H_
+
+#include <linux/types.h>
+#include <linux/platform_device.h>
+
+#define GSI_RING_ELEMENT_SIZE	16	/* bytes (channel or event ring) */
+
+/**
+ * enum gsi_xfer_flag - Transfer element flag values.
+ * @GSI_XFER_FLAG_CHAIN:	Not the last element in a transaction.
+ * @GSI_XFER_FLAG_EOB:		Generate event interrupt when complete.
+ * @GSI_XFER_FLAG_EOT:		Interrupt on end of transfer condition.
+ * @GSI_XFER_FLAG_BEI:		Block (do not generate) event interrupt.
+ *
+ * Normally an event generated by completion of a transfer will cause
+ * the AP to be interrupted; the BEI flag prevents that.
+ */
+enum gsi_xfer_flag {
+	GSI_XFER_FLAG_CHAIN	= BIT(1),
+	GSI_XFER_FLAG_EOB	= BIT(2),
+	GSI_XFER_FLAG_EOT	= BIT(3),
+	GSI_XFER_FLAG_BEI	= BIT(4),
+};
+
+/**
+ * enum gsi_xfer_elem_type - Transfer element type.
+ * @GSI_XFER_ELEM_DATA:		Element represents a data transfer
+ * @GSI_XFER_ELEM_IMME_CMD:	Element contains an immediate command.
+ * @GSI_XFER_ELEM_NOP:		Element contans a no-op command.
+ */
+enum gsi_xfer_elem_type {
+	GSI_XFER_ELEM_DATA,
+	GSI_XFER_ELEM_IMME_CMD,
+	GSI_XFER_ELEM_NOP,
+};
+
+/**
+ * gsi_xfer_elem - Description of a single transfer.
+ * @addr:	Physical address of a buffer for data or immediate commands.
+ * @len_opcode:	Length of the data buffer, or enum ipahal_imm_cmd opcode
+ * @flags:	Flags for the transfer
+ * @type:	Command type (immediate command, data transfer NOP)
+ * @user_data:	Data maintained for (but unused by) the transfer element.
+ */
+struct gsi_xfer_elem {
+	u64 addr;
+	u16 len_opcode;
+	enum gsi_xfer_flag flags;
+	enum gsi_xfer_elem_type type;
+	void *user_data;
+};
+
+struct gsi;
+
+/**
+ * gsi_init() - Initialize GSI subsystem
+ * @pdev:	IPA platform device, to look up resources
+ *
+ * This stage of initialization can occur before the GSI firmware
+ * has been loaded.
+ *
+ * Return:	GSI pointer to provide to other GSI functions.
+ */
+struct gsi *gsi_init(struct platform_device *pdev);
+
+/**
+ * gsi_device_init() - Initialize a GSI device
+ * @gsi:	GSI pointer returned by gsi_init()
+ *
+ * Initialize a GSI device.
+ *
+ * @Return:	0 if successful or a negative error code otherwise.
+ */
+int gsi_device_init(struct gsi *gsi);
+
+/**
+ * gsi_device_exit() - De-initialize a GSI device
+ * @gsi:	GSI pointer returned by gsi_init()
+ *
+ * This is the inverse of gsi_device_init()
+ */
+void gsi_device_exit(struct gsi *gsi);
+
+/**
+ * gsi_channel_alloc() - Allocate a GSI channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to allocate
+ * @channel_count: Number of transfer element slots in the channel
+ * @from_ipa:	Direction of data transfer (true: IPA->AP; false: AP->IPA)
+ * @priority:	Whether this channel will given prioroity
+ * @evt_ring_mult: Factor to use to get the number of elements in the
+ *		event ring associated with this channel
+ * @moderation:	Whether interrupt moderation should be enabled
+ * @notify_data: Pointer value to supply with notifications that
+ * 		occur because of events on this channel
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_alloc(struct gsi *gsi, u32 channel_id, u32 channel_count,
+		      bool from_ipa, bool priority, u32 evt_ring_mult,
+		      bool moderation, void *notify_data);
+
+/**
+ * gsi_channel_scratch_write() - Write channel scratch area
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel whose scratch area should be written
+ * @tlv_count:	The number of type-length-value the channel uses
+ */
+void gsi_channel_scratch_write(struct gsi *gsi, u32 channel_id, u32 tlv_count);
+
+/**
+ * gsi_channel_start() - Make a channel operational
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to start
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_start(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_stop() - Stop an operational channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to stop
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_stop(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_reset() - Reset a channel, to recover from error state
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to be reset
+ *
+ * @Return:	 0 if successful, or a negative error code.
+ */
+int gsi_channel_reset(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_free() - Release a previously-allocated channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to be freed
+ */
+void gsi_channel_free(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_config() - Configure a channel
+ * @gsi:		GSI pointer returned by gsi_init()
+ * @channel_id:		Channel to be configured
+ * @doorbell_enable:	Whether to enable hardware doorbell engine
+ */
+void gsi_channel_config(struct gsi *gsi, u32 channel_id, bool doorbell_enable);
+
+/**
+ * gsi_channel_poll() - Poll for a single completion on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel to be polled
+ *
+ * @Return:	Byte transfer count if successful, or a negative error code
+ */
+int gsi_channel_poll(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_intr_enable() - Enable interrupts on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel whose interrupts should be enabled
+ */
+void gsi_channel_intr_enable(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_intr_disable() - Disable interrupts on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel whose interrupts should be disabled
+ */
+void gsi_channel_intr_disable(struct gsi *gsi, u32 channel_id);
+
+/**
+ * gsi_channel_queue() - Queue transfer requests on a channel
+ * @gsi:	GSI pointer returned by gsi_init()
+ * @channel_id:	Channel on which transfers should be queued
+ * @num_xfers:	Number of transfer descriptors in the @xfer array
+ * @xfer:	Array of transfer descriptors
+ * @ring_db:	Whether to tell the hardware about these queued transfers
+ *
+ * @Return:	0 if successful, or a negative error code
+ */
+int gsi_channel_queue(struct gsi *gsi, u32 channel_id, u16 num_xfers,
+		      struct gsi_xfer_elem *xfer, bool ring_db);
+
+#endif /* _GSI_H_ */
diff --git a/drivers/net/ipa/gsi_reg.h b/drivers/net/ipa/gsi_reg.h
new file mode 100644
index 000000000000..fe5f98ef3840
--- /dev/null
+++ b/drivers/net/ipa/gsi_reg.h
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef __GSI_REG_H__
+#define __GSI_REG_H__
+
+/* The maximum allowed value of "n" for any N-parameterized macro below
+ * is 3.  The N value comes from the ipa_ees enumerated type.
+ *
+ * For GSI_INST_RAM_I_OFFS(), the "i" value supplied is an instruction
+ * offset (where each instruction is 32 bits wide).  The maximum offset
+ * value is 4095.
+ *
+ * Macros parameterized by (data) channel number supply a parameter "c".
+ * The maximum value of "c" is 30 (but the limit is hardware-dependent).
+ *
+ * Macros parameterized by event channel number supply a parameter "e".
+ * The maximum value of "e" is 15 (but the limit is hardware-dependent).
+ *
+ * For any K-parameterized macros, the "k" value will represent either an
+ * event ring id or a (data) channel id.  15 is the maximum value of
+ * "k" for event rings; otherwise the maximum is 30.
+ */
+#define GSI_CFG_OFFS				0x00000000
+#define GSI_ENABLE_FMASK			0x00000001
+#define MCS_ENABLE_FMASK			0x00000002
+#define DOUBLE_MCS_CLK_FREQ_FMASK		0x00000004
+#define UC_IS_MCS_FMASK				0x00000008
+#define PWR_CLPS_FMASK				0x00000010
+#define BP_MTRIX_DISABLE_FMASK			0x00000020
+
+#define GSI_MCS_CFG_OFFS			0x0000b000
+#define MCS_CFG_ENABLE_FMASK			0x00000001
+
+#define GSI_PERIPH_BASE_ADDR_LSB_OFFS		0x00000018
+
+#define GSI_PERIPH_BASE_ADDR_MSB_OFFS		0x0000001c
+
+#define GSI_IC_DISABLE_CHNL_BCK_PRS_LSB_OFFS	0x000000a0
+#define CHNL_REE_INT_FMASK			0x00000007
+#define CHNL_EV_ENG_INT_FMASK			0x00000040
+#define CHNL_INT_END_INT_FMASK			0x00001000
+#define CHNL_CSR_INT_FMASK			0x00fc0000
+#define CHNL_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_DISABLE_CHNL_BCK_PRS_MSB_OFFS	0x000000a4
+#define CHNL_TIMER_INT_FMASK			0x00000001
+#define CHNL_DB_ENG_INT_FMASK			0x00000040
+#define CHNL_RD_WR_INT_FMASK			0x00003000
+#define CHNL_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_GEN_EVNT_BCK_PRS_LSB_OFFS	0x000000a8
+#define EVT_REE_INT_FMASK			0x00000007
+#define EVT_EV_ENG_INT_FMASK			0x00000040
+#define EVT_INT_END_INT_FMASK			0x00001000
+#define EVT_CSR_INT_FMASK			0x00fc0000
+#define EVT_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_GEN_EVNT_BCK_PRS_MSB_OFFS	0x000000ac
+#define EVT_TIMER_INT_FMASK			0x00000001
+#define EVT_DB_ENG_INT_FMASK			0x00000040
+#define EVT_RD_WR_INT_FMASK			0x00003000
+#define EVT_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_GEN_INT_BCK_PRS_LSB_OFFS		0x000000b0
+#define INT_REE_INT_FMASK			0x00000007
+#define INT_EV_ENG_INT_FMASK			0x00000040
+#define INT_INT_END_INT_FMASK			0x00001000
+#define INT_CSR_INT_FMASK			0x00fc0000
+#define INT_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_GEN_INT_BCK_PRS_MSB_OFFS		0x000000b4
+#define INT_TIMER_INT_FMASK			0x00000001
+#define INT_DB_ENG_INT_FMASK			0x00000040
+#define INT_RD_WR_INT_FMASK			0x00003000
+#define INT_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_STOP_INT_MOD_BCK_PRS_LSB_OFFS	0x000000b8
+#define REE_INT_FMASK				0x00000007
+#define EV_ENG_INT_FMASK			0x00000040
+#define INT_END_INT_FMASK			0x00001000
+#define CSR_INT_FMASK				0x00fc0000
+#define TLV_INT_FMASK				0x3f000000
+
+#define GSI_IC_STOP_INT_MOD_BCK_PRS_MSB_OFFS	0x000000bc
+#define TIMER_INT_FMASK				0x00000001
+#define DB_ENG_INT_FMASK			0x00000040
+#define RD_WR_INT_FMASK				0x00003000
+#define UCONTROLLER_INT_FMASK			0x00fc0000
+
+#define GSI_IC_PROCESS_DESC_BCK_PRS_LSB_OFFS	0x000000c0
+#define DESC_REE_INT_FMASK			0x00000007
+#define DESC_EV_ENG_INT_FMASK			0x00000040
+#define DESC_INT_END_INT_FMASK			0x00001000
+#define DESC_CSR_INT_FMASK			0x00fc0000
+#define DESC_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_PROCESS_DESC_BCK_PRS_MSB_OFFS	0x000000c4
+#define DESC_TIMER_INT_FMASK			0x00000001
+#define DESC_DB_ENG_INT_FMASK			0x00000040
+#define DESC_RD_WR_INT_FMASK			0x00003000
+#define DESC_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_TLV_STOP_BCK_PRS_LSB_OFFS	0x000000c8
+#define STOP_REE_INT_FMASK			0x00000007
+#define STOP_EV_ENG_INT_FMASK			0x00000040
+#define STOP_INT_END_INT_FMASK			0x00001000
+#define STOP_CSR_INT_FMASK			0x00fc0000
+#define STOP_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_TLV_STOP_BCK_PRS_MSB_OFFS	0x000000cc
+#define STOP_TIMER_INT_FMASK			0x00000001
+#define STOP_DB_ENG_INT_FMASK			0x00000040
+#define STOP_RD_WR_INT_FMASK			0x00003000
+#define STOP_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_TLV_RESET_BCK_PRS_LSB_OFFS	0x000000d0
+#define RST_REE_INT_FMASK			0x00000007
+#define RST_EV_ENG_INT_FMASK			0x00000040
+#define RST_INT_END_INT_FMASK			0x00001000
+#define RST_CSR_INT_FMASK			0x00fc0000
+#define RST_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_TLV_RESET_BCK_PRS_MSB_OFFS	0x000000d4
+#define RST_TIMER_INT_FMASK			0x00000001
+#define RST_DB_ENG_INT_FMASK			0x00000040
+#define RST_RD_WR_INT_FMASK			0x00003000
+#define RST_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_RGSTR_TIMER_BCK_PRS_LSB_OFFS	0x000000d8
+#define TMR_REE_INT_FMASK			0x00000007
+#define TMR_EV_ENG_INT_FMASK			0x00000040
+#define TMR_INT_END_INT_FMASK			0x00001000
+#define TMR_CSR_INT_FMASK			0x00fc0000
+#define TMR_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_RGSTR_TIMER_BCK_PRS_MSB_OFFS	0x000000dc
+#define TMR_TIMER_INT_FMASK			0x00000001
+#define TMR_DB_ENG_INT_FMASK			0x00000040
+#define TMR_RD_WR_INT_FMASK			0x00003000
+#define TMR_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_READ_BCK_PRS_LSB_OFFS		0x000000e0
+#define RD_REE_INT_FMASK			0x00000007
+#define RD_EV_ENG_INT_FMASK			0x00000040
+#define RD_INT_END_INT_FMASK			0x00001000
+#define RD_CSR_INT_FMASK			0x00fc0000
+#define RD_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_READ_BCK_PRS_MSB_OFFS		0x000000e4
+#define RD_TIMER_INT_FMASK			0x00000001
+#define RD_DB_ENG_INT_FMASK			0x00000040
+#define RD_RD_WR_INT_FMASK			0x00003000
+#define RD_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_WRITE_BCK_PRS_LSB_OFFS		0x000000e8
+#define WR_REE_INT_FMASK			0x00000007
+#define WR_EV_ENG_INT_FMASK			0x00000040
+#define WR_INT_END_INT_FMASK			0x00001000
+#define WR_CSR_INT_FMASK			0x00fc0000
+#define WR_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_WRITE_BCK_PRS_MSB_OFFS		0x000000ec
+#define WR_TIMER_INT_FMASK			0x00000001
+#define WR_DB_ENG_INT_FMASK			0x00000040
+#define WR_RD_WR_INT_FMASK			0x00003000
+#define WR_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IC_UCONTROLLER_GPR_BCK_PRS_LSB_OFFS	0x000000f0
+#define UC_REE_INT_FMASK			0x00000007
+#define UC_EV_ENG_INT_FMASK			0x00000040
+#define UC_INT_END_INT_FMASK			0x00001000
+#define UC_CSR_INT_FMASK			0x00fc0000
+#define UC_TLV_INT_FMASK			0x3f000000
+
+#define GSI_IC_UCONTROLLER_GPR_BCK_PRS_MSB_OFFS	0x000000f4
+#define UC_TIMER_INT_FMASK			0x00000001
+#define UC_DB_ENG_INT_FMASK			0x00000040
+#define UC_RD_WR_INT_FMASK			0x00003000
+#define UC_UCONTROLLER_INT_FMASK		0x00fc0000
+
+#define GSI_IRAM_PTR_CH_CMD_OFFS		0x00000400
+#define CMD_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_EE_GENERIC_CMD_OFFS	0x00000404
+#define EE_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_CH_DB_OFFS			0x00000418
+#define CH_DB_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_EV_DB_OFFS			0x0000041c
+#define EV_DB_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_NEW_RE_OFFS		0x00000420
+#define NEW_RE_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_CH_DIS_COMP_OFFS		0x00000424
+#define DIS_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_CH_EMPTY_OFFS		0x00000428
+#define EMPTY_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_EVENT_GEN_COMP_OFFS	0x0000042c
+#define EVT_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_PERIPH_IF_TLV_IN_0_OFFS	0x00000430
+#define IN_0_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_PERIPH_IF_TLV_IN_2_OFFS	0x00000434
+#define IN_2_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_PERIPH_IF_TLV_IN_1_OFFS	0x00000438
+#define IN_1_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_TIMER_EXPIRED_OFFS		0x0000043c
+#define TMR_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_WRITE_ENG_COMP_OFFS	0x00000440
+#define WR_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_READ_ENG_COMP_OFFS		0x00000444
+#define RD_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_UC_GP_INT_OFFS		0x00000448
+#define UC_IRAM_PTR_FMASK			0x00000fff
+
+#define GSI_IRAM_PTR_INT_MOD_STOPPED_OFFS	0x0000044c
+#define STOP_IRAM_PTR_FMASK			0x00000fff
+
+/* Max value of I for the GSI_INST_RAM_I_OFFS() is 4095 */
+#define GSI_INST_RAM_I_OFFS(i)			(0x00004000 + 0x0004 * (i))
+#define INST_BYTE_0_FMASK			0x000000ff
+#define INST_BYTE_1_FMASK			0x0000ff00
+#define INST_BYTE_2_FMASK			0x00ff0000
+#define INST_BYTE_3_FMASK			0xff000000
+
+#define GSI_CH_C_CNTXT_0_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_0_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_0_OFFS(c, n) \
+					(0x0001c000 + 0x4000 * (n) + 0x80 * (c))
+#define CHTYPE_PROTOCOL_FMASK			0x00000007
+#define CHTYPE_DIR_FMASK			0x00000008
+#define EE_FMASK				0x000000f0
+#define CHID_FMASK				0x00001f00
+#define ERINDEX_FMASK				0x0007c000
+#define CHSTATE_FMASK				0x00f00000
+#define ELEMENT_SIZE_FMASK			0xff000000
+
+#define GSI_CH_C_CNTXT_1_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_1_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_1_OFFS(c, n) \
+					(0x0001c004 + 0x4000 * (n) + 0x80 * (c))
+#define R_LENGTH_FMASK				0x0000ffff
+
+#define GSI_CH_C_CNTXT_2_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_2_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_2_OFFS(c, n) \
+					(0x0001c008 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_CNTXT_3_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_3_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_3_OFFS(c, n) \
+					(0x0001c00c + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_CNTXT_4_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_4_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_4_OFFS(c, n) \
+					(0x0001c010 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_CNTXT_6_OFFS(c) \
+				GSI_EE_N_CH_C_CNTXT_6_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_CNTXT_6_OFFS(c, n) \
+					(0x0001c018 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_QOS_OFFS(c)	GSI_EE_N_CH_C_QOS_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_QOS_OFFS(c, n)	(0x0001c05c + 0x4000 * (n) + 0x80 * (c))
+#define WRR_WEIGHT_FMASK			0x0000000f
+#define MAX_PREFETCH_FMASK			0x00000100
+#define USE_DB_ENG_FMASK			0x00000200
+
+#define GSI_CH_C_SCRATCH_0_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_0_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_0_OFFS(c, n) \
+					(0x0001c060 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_SCRATCH_1_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_1_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_1_OFFS(c, n) \
+					(0x0001c064 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_SCRATCH_2_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_2_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_2_OFFS(c, n) \
+					(0x0001c068 + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_CH_C_SCRATCH_3_OFFS(c) \
+				GSI_EE_N_CH_C_SCRATCH_3_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_SCRATCH_3_OFFS(c, n) \
+					(0x0001c06c + 0x4000 * (n) + 0x80 * (c))
+
+#define GSI_EV_CH_E_CNTXT_0_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_0_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_0_OFFS(e, n) \
+					(0x0001d000 + 0x4000 * (n) + 0x80 * (e))
+#define EV_CHTYPE_FMASK				0x0000000f
+#define EV_EE_FMASK				0x000000f0
+#define EV_EVCHID_FMASK				0x0000ff00
+#define EV_INTYPE_FMASK				0x00010000
+#define EV_CHSTATE_FMASK			0x00f00000
+#define EV_ELEMENT_SIZE_FMASK			0xff000000
+
+#define GSI_EV_CH_E_CNTXT_1_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_1_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_1_OFFS(e, n) \
+					(0x0001d004 + 0x4000 * (n) + 0x80 * (e))
+#define EV_R_LENGTH_FMASK			0x0000ffff
+
+#define GSI_EV_CH_E_CNTXT_2_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_2_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_2_OFFS(e, n) \
+					(0x0001d008 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_3_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_3_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_3_OFFS(e, n) \
+					(0x0001d00c + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_4_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_4_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_4_OFFS(e, n) \
+					(0x0001d010 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_8_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_8_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_8_OFFS(e, n) \
+					(0x0001d020 + 0x4000 * (n) + 0x80 * (e))
+#define MODT_FMASK				0x0000ffff
+#define MODC_FMASK				0x00ff0000
+#define MOD_CNT_FMASK				0xff000000
+
+#define GSI_EV_CH_E_CNTXT_9_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_9_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_9_OFFS(e, n) \
+					(0x0001d024 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_10_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_10_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_10_OFFS(e, n) \
+					(0x0001d028 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_11_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_11_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_11_OFFS(e, n) \
+					(0x0001d02c + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_12_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_12_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_12_OFFS(e, n) \
+					(0x0001d030 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_CNTXT_13_OFFS(e) \
+				GSI_EE_N_EV_CH_E_CNTXT_13_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_CNTXT_13_OFFS(e, n) \
+					(0x0001d034 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_SCRATCH_0_OFFS(e) \
+				GSI_EE_N_EV_CH_E_SCRATCH_0_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_SCRATCH_0_OFFS(e, n) \
+					(0x0001d048 + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_EV_CH_E_SCRATCH_1_OFFS(e) \
+				GSI_EE_N_EV_CH_E_SCRATCH_1_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_SCRATCH_1_OFFS(e, n) \
+					(0x0001d04c + 0x4000 * (n) + 0x80 * (e))
+
+#define GSI_CH_C_DOORBELL_0_OFFS(c) \
+				GSI_EE_N_CH_C_DOORBELL_0_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_DOORBELL_0_OFFS(c, n) \
+					(0x0001e000 + 0x4000 * (n) + 0x08 * (c))
+
+#define GSI_CH_C_DOORBELL_1_OFFS(c) \
+				GSI_EE_N_CH_C_DOORBELL_1_OFFS(c, IPA_EE_AP)
+#define GSI_EE_N_CH_C_DOORBELL_1_OFFS(c, n) \
+					(0x0001e004 + 0x4000 * (n) + 0x08 * (c))
+
+#define GSI_EV_CH_E_DOORBELL_0_OFFS(e) \
+				GSI_EE_N_EV_CH_E_DOORBELL_0_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_DOORBELL_0_OFFS(e, n) \
+					(0x0001e100 + 0x4000 * (n) + 0x08 * (e))
+
+#define GSI_EV_CH_E_DOORBELL_1_OFFS(e) \
+				GSI_EE_N_EV_CH_E_DOORBELL_1_OFFS(e, IPA_EE_AP)
+#define GSI_EE_N_EV_CH_E_DOORBELL_1_OFFS(e, n) \
+					(0x0001e104 + 0x4000 * (n) + 0x08 * (e))
+
+#define GSI_GSI_STATUS_OFFS	GSI_EE_N_GSI_STATUS_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_STATUS_OFFS(n)		(0x0001f000 + 0x4000 * (n))
+#define ENABLED_FMASK				0x00000001
+
+#define GSI_CH_CMD_OFFS		GSI_EE_N_CH_CMD_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CH_CMD_OFFS(n)			(0x0001f008 + 0x4000 * (n))
+#define CH_CHID_FMASK				0x000000ff
+#define CH_OPCODE_FMASK				0xff000000
+
+#define GSI_EV_CH_CMD_OFFS	GSI_EE_N_EV_CH_CMD_OFFS(IPA_EE_AP)
+#define GSI_EE_N_EV_CH_CMD_OFFS(n)		(0x0001f010 + 0x4000 * (n))
+#define EV_CHID_FMASK				0x000000ff
+#define EV_OPCODE_FMASK				0xff000000
+
+#define GSI_GSI_HW_PARAM_2_OFFS	GSI_EE_N_GSI_HW_PARAM_2_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_HW_PARAM_2_OFFS(n)		(0x0001f040 + 0x4000 * (n))
+#define IRAM_SIZE_FMASK				0x00000007
+#define NUM_CH_PER_EE_FMASK			0x000000f8
+#define NUM_EV_PER_EE_FMASK			0x00001f00
+#define GSI_CH_PEND_TRANSLATE_FMASK		0x00002000
+#define GSI_CH_FULL_LOGIC_FMASK			0x00004000
+#define IRAM_SIZE_ONE_KB_FVAL			0
+#define IRAM_SIZE_TWO_KB_FVAL			1
+
+#define GSI_GSI_SW_VERSION_OFFS	GSI_EE_N_GSI_SW_VERSION_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_SW_VERSION_OFFS(n)		(0x0001f044 + 0x4000 * (n))
+#define STEP_FMASK				0x0000ffff
+#define MINOR_FMASK				0x0fff0000
+#define MAJOR_FMASK				0xf0000000
+
+#define GSI_GSI_MCS_CODE_VER_OFFS \
+				GSI_EE_N_GSI_MCS_CODE_VER_OFFS(IPA_EE_AP)
+#define GSI_EE_N_GSI_MCS_CODE_VER_OFFS(n)	(0x0001f048 + 0x4000 * (n))
+
+#define GSI_CNTXT_TYPE_IRQ_OFFS	GSI_EE_N_CNTXT_TYPE_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_TYPE_IRQ_OFFS(n)		(0x0001f080 + 0x4000 * (n))
+#define CH_CTRL_FMASK				0x00000001
+#define EV_CTRL_FMASK				0x00000002
+#define GLOB_EE_FMASK				0x00000004
+#define IEOB_FMASK				0x00000008
+#define INTER_EE_CH_CTRL_FMASK			0x00000010
+#define INTER_EE_EV_CTRL_FMASK			0x00000020
+#define GENERAL_FMASK				0x00000040
+
+#define GSI_CNTXT_TYPE_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_TYPE_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_TYPE_IRQ_MSK_OFFS(n)	(0x0001f088 + 0x4000 * (n))
+#define MSK_CH_CTRL_FMASK			0x00000001
+#define MSK_EV_CTRL_FMASK			0x00000002
+#define MSK_GLOB_EE_FMASK			0x00000004
+#define MSK_IEOB_FMASK				0x00000008
+#define MSK_INTER_EE_CH_CTRL_FMASK		0x00000010
+#define MSK_INTER_EE_EV_CTRL_FMASK		0x00000020
+#define MSK_GENERAL_FMASK			0x00000040
+
+#define GSI_CNTXT_SRC_CH_IRQ_OFFS \
+				GSI_EE_N_CNTXT_SRC_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_CH_IRQ_OFFS(n)	(0x0001f090 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_EV_CH_IRQ_OFFS \
+				GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_OFFS(n)	(0x0001f094 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_CH_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_SRC_CH_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_CH_IRQ_MSK_OFFS(n)	(0x0001f098 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_MSK_OFFS(n) (0x0001f09c + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_CH_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_SRC_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_CH_IRQ_CLR_OFFS(n)	(0x0001f0a0 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_EV_CH_IRQ_CLR_OFFS(n) (0x0001f0a4 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_IEOB_IRQ_OFFS \
+				GSI_EE_N_CNTXT_SRC_IEOB_IRQ_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_IEOB_IRQ_OFFS(n)	(0x0001f0b0 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFS \
+				GSI_EE_N_CNTXT_SRC_IEOB_IRQ_MSK_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_IEOB_IRQ_MSK_OFFS(n)	(0x0001f0b8 + 0x4000 * (n))
+
+#define GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_SRC_IEOB_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_SRC_IEOB_IRQ_CLR_OFFS(n)	(0x0001f0c0 + 0x4000 * (n))
+
+#define GSI_CNTXT_GLOB_IRQ_STTS_OFFS \
+				GSI_EE_N_CNTXT_GLOB_IRQ_STTS_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GLOB_IRQ_STTS_OFFS(n)	(0x0001f100 + 0x4000 * (n))
+#define ERROR_INT_FMASK				0x00000001
+#define GP_INT1_FMASK				0x00000002
+#define GP_INT2_FMASK				0x00000004
+#define GP_INT3_FMASK				0x00000008
+
+#define GSI_CNTXT_GLOB_IRQ_EN_OFFS \
+				GSI_EE_N_CNTXT_GLOB_IRQ_EN_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GLOB_IRQ_EN_OFFS(n)	(0x0001f108 + 0x4000 * (n))
+#define EN_ERROR_INT_FMASK			0x00000001
+#define EN_GP_INT1_FMASK			0x00000002
+#define EN_GP_INT2_FMASK			0x00000004
+#define EN_GP_INT3_FMASK			0x00000008
+
+#define GSI_CNTXT_GLOB_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_GLOB_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GLOB_IRQ_CLR_OFFS(n)	(0x0001f110 + 0x4000 * (n))
+#define CLR_ERROR_INT_FMASK			0x00000001
+#define CLR_GP_INT1_FMASK			0x00000002
+#define CLR_GP_INT2_FMASK			0x00000004
+#define CLR_GP_INT3_FMASK			0x00000008
+
+#define GSI_CNTXT_GSI_IRQ_STTS_OFFS \
+				GSI_EE_N_CNTXT_GSI_IRQ_STTS_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GSI_IRQ_STTS_OFFS(n)	(0x0001f118 + 0x4000 * (n))
+#define BREAK_POINT_FMASK			0x00000001
+#define BUS_ERROR_FMASK				0x00000002
+#define CMD_FIFO_OVRFLOW_FMASK			0x00000004
+#define MCS_STACK_OVRFLOW_FMASK			0x00000008
+
+#define GSI_CNTXT_GSI_IRQ_EN_OFFS \
+				GSI_EE_N_CNTXT_GSI_IRQ_EN_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GSI_IRQ_EN_OFFS(n)	(0x0001f120 + 0x4000 * (n))
+#define EN_BREAK_POINT_FMASK			0x00000001
+#define EN_BUS_ERROR_FMASK			0x00000002
+#define EN_CMD_FIFO_OVRFLOW_FMASK		0x00000004
+#define EN_MCS_STACK_OVRFLOW_FMASK		0x00000008
+
+#define GSI_CNTXT_GSI_IRQ_CLR_OFFS \
+				GSI_EE_N_CNTXT_GSI_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_CNTXT_GSI_IRQ_CLR_OFFS(n)	(0x0001f128 + 0x4000 * (n))
+#define CLR_BREAK_POINT_FMASK			0x00000001
+#define CLR_BUS_ERROR_FMASK			0x00000002
+#define CLR_CMD_FIFO_OVRFLOW_FMASK		0x00000004
+#define CLR_MCS_STACK_OVRFLOW_FMASK		0x00000008
+
+#define GSI_EE_N_CNTXT_INTSET_OFFS(n)		(0x0001f180 + 0x4000 * (n))
+#define INTYPE_FMASK				0x00000001
+#define GSI_CNTXT_INTSET_OFFS	GSI_EE_N_CNTXT_INTSET_OFFS(IPA_EE_AP)
+
+#define GSI_ERROR_LOG_OFFS	GSI_EE_N_ERROR_LOG_OFFS(IPA_EE_AP)
+#define GSI_EE_N_ERROR_LOG_OFFS(n)		(0x0001f200 + 0x4000 * (n))
+
+#define GSI_ERROR_LOG_CLR_OFFS	GSI_EE_N_ERROR_LOG_CLR_OFFS(IPA_EE_AP)
+#define GSI_EE_N_ERROR_LOG_CLR_OFFS(n)		(0x0001f210 + 0x4000 * (n))
+
+#define GSI_INTER_EE_SRC_CH_IRQ_OFFS \
+				GSI_INTER_EE_N_SRC_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_CH_IRQ_OFFS(n)	(0x0000c018 + 0x1000 * (n))
+
+#define GSI_INTER_EE_SRC_EV_CH_IRQ_OFFS \
+				GSI_INTER_EE_N_SRC_EV_CH_IRQ_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_EV_CH_IRQ_OFFS(n)	(0x0000c01c + 0x1000 * (n))
+
+#define GSI_INTER_EE_SRC_CH_IRQ_CLR_OFFS \
+				GSI_INTER_EE_N_SRC_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_CH_IRQ_CLR_OFFS(n)	(0x0000c028 + 0x1000 * (n))
+
+#define GSI_INTER_EE_SRC_EV_CH_IRQ_CLR_OFFS \
+				GSI_INTER_EE_N_SRC_EV_CH_IRQ_CLR_OFFS(IPA_EE_AP)
+#define GSI_INTER_EE_N_SRC_EV_CH_IRQ_CLR_OFFS(n) (0x0000c02c + 0x1000 * (n))
+
+#endif	/* _GSI_REG_H__ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 04/12] soc: qcom: ipa: immediate commands
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This patch contains (mostly) code implementing "immediate commands."
(The source files are still named "ipahal" for historical reasons.)

One channel (APPS CMD_PROD) is used for sending commands *to* the
IPA itself, rather than passing data through it.  These immediate
commands are issued to the IPA using the normal GSI queueing
mechanism.  And each command's completion is handled using the
normal GSI transfer completion mechanisms.

In addition to immediate commands, the "IPA HAL" includes code for
interpreting status packets that are supplied to the IPA on consumer
channels.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipahal.c | 541 +++++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipahal.h | 253 ++++++++++++++++++
 2 files changed, 794 insertions(+)
 create mode 100644 drivers/net/ipa/ipahal.c
 create mode 100644 drivers/net/ipa/ipahal.h

diff --git a/drivers/net/ipa/ipahal.c b/drivers/net/ipa/ipahal.c
new file mode 100644
index 000000000000..de00bcd54d4f
--- /dev/null
+++ b/drivers/net/ipa/ipahal.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include "ipahal.h"
+#include "ipa_i.h"	/* ipa_err() */
+#include "ipa_dma.h"
+
+/**
+ * DOC:  IPA Immediate Commands
+ *
+ * The APPS_CMD_PROD channel is used to issue immediate commands to
+ * the IPA.  An immediate command is generally used to request the
+ * IPA do something other than data transfer.
+ *
+ * An immediate command is represented by a GSI transfer element.
+ * Each immediate command has a well-defined format, with a known
+ * length.  The transfer element's length field can therefore be
+ * used to hold a command's opcode.  The "payload" of an immediate
+ * command contains additional information required for the command.
+ * It resides in DRAM and is referred to using the DMA memory data
+ * pointer (the same one used to refer to the data in a "normal"
+ * transfer).
+ *
+ * Immediate commands are issued to the IPA through the APPS_CMD_PROD
+ * channel using the normal GSI queueing mechanism.  And each command's
+ * completion is handled using the normal GSI transfer completion
+ * mechanisms.
+ */
+
+/**
+ * struct ipahal_context - HAL global context data
+ * @empty_fltrt_tbl:	Empty table to be used for table initialization
+ */
+static struct ipahal_context {
+	struct ipa_dma_mem empty_fltrt_tbl;
+} ipahal_ctx_struct;
+static struct ipahal_context *ipahal_ctx = &ipahal_ctx_struct;
+
+/* enum ipa_pipeline_clear_option - Values for pipeline clear waiting options
+ * @IPAHAL_HPS_CLEAR: Wait for HPS clear. All queues except high priority queue
+ *  shall not be serviced until HPS is clear of packets or immediate commands.
+ *  The high priority Rx queue / Q6ZIP group shall still be serviced normally.
+ *
+ * @IPAHAL_SRC_GRP_CLEAR: Wait for originating source group to be clear
+ *  (for no packet contexts allocated to the originating source group).
+ *  The source group / Rx queue shall not be serviced until all previously
+ *  allocated packet contexts are released. All other source groups/queues shall
+ *  be serviced normally.
+ *
+ * @IPAHAL_FULL_PIPELINE_CLEAR: Wait for full pipeline to be clear.
+ *  All groups / Rx queues shall not be serviced until IPA pipeline is fully
+ *  clear. This should be used for debug only.
+ *
+ *  The values assigned to these are assumed by the REGISTER_WRITE
+ *  (struct ipa_imm_cmd_hw_register_write) and the DMA_SHARED_MEM
+ *  (struct ipa_imm_cmd_hw_dma_shared_mem) immediate commands for
+ *  IPA version 3 hardware.  They are also used to modify the opcode
+ *  used to implement these commands for IPA version 4 hardware.
+ */
+enum ipahal_pipeline_clear_option {
+	IPAHAL_HPS_CLEAR		= 0,
+	IPAHAL_SRC_GRP_CLEAR		= 1,
+	IPAHAL_FULL_PIPELINE_CLEAR	= 2,
+};
+
+/* Immediate commands H/W structures */
+
+/* struct ipa_imm_cmd_hw_ip_fltrt_init - IP_V*_FILTER_INIT/IP_V*_ROUTING_INIT
+ * command payload in H/W format.
+ * Inits IPv4/v6 routing or filter block.
+ * @hash_rules_addr: Addr in system mem where hashable flt/rt rules starts
+ * @hash_rules_size: Size in bytes of the hashable tbl to cpy to local mem
+ * @hash_local_addr: Addr in shared mem where hashable flt/rt tbl should
+ *  be copied to
+ * @nhash_rules_size: Size in bytes of the non-hashable tbl to cpy to local mem
+ * @nhash_local_addr: Addr in shared mem where non-hashable flt/rt tbl should
+ *  be copied to
+ * @rsvd: reserved
+ * @nhash_rules_addr: Addr in sys mem where non-hashable flt/rt tbl starts
+ */
+struct ipa_imm_cmd_hw_ip_fltrt_init {
+	u64 hash_rules_addr;
+	u64 hash_rules_size	: 12,
+	    hash_local_addr	: 16,
+	    nhash_rules_size	: 12,
+	    nhash_local_addr	: 16,
+	    rsvd		: 8;
+	u64 nhash_rules_addr;
+};
+
+/* struct ipa_imm_cmd_hw_hdr_init_local - HDR_INIT_LOCAL command payload
+ *  in H/W format.
+ * Inits hdr table within local mem with the hdrs and their length.
+ * @hdr_table_addr: Word address in sys mem where the table starts (SRC)
+ * @size_hdr_table: Size of the above (in bytes)
+ * @hdr_addr: header address in IPA sram (used as DST for memory copy)
+ * @rsvd: reserved
+ */
+struct ipa_imm_cmd_hw_hdr_init_local {
+	u64 hdr_table_addr;
+	u32 size_hdr_table	: 12,
+	    hdr_addr		: 16,
+	    rsvd		: 4;
+};
+
+/* struct ipa_imm_cmd_hw_dma_shared_mem - DMA_SHARED_MEM command payload
+ *  in H/W format.
+ * Perform mem copy into or out of the SW area of IPA local mem
+ * @sw_rsvd: Ignored by H/W. My be used by S/W
+ * @size: Size in bytes of data to copy. Expected size is up to 2K bytes
+ * @local_addr: Address in IPA local memory
+ * @direction: Read or write?
+ *	0: IPA write, Write to local address from system address
+ *	1: IPA read, Read from local address to system address
+ * @skip_pipeline_clear: 0 to wait until IPA pipeline is clear. 1 don't wait
+ * @pipeline_clear_options: options for pipeline to clear
+ *	0: HPS - no pkt inside HPS (not grp specific)
+ *	1: source group - The immediate cmd src grp does npt use any pkt ctxs
+ *	2: Wait until no pkt reside inside IPA pipeline
+ *	3: reserved
+ * @rsvd: reserved - should be set to zero
+ * @system_addr: Address in system memory
+ */
+struct ipa_imm_cmd_hw_dma_shared_mem {
+	u16 sw_rsvd;
+	u16 size;
+	u16 local_addr;
+	u16 direction			: 1,
+	    skip_pipeline_clear		: 1,
+	    pipeline_clear_options	: 2,
+	    rsvd			: 12;
+	u64 system_addr;
+};
+
+/* struct ipa_imm_cmd_hw_dma_task_32b_addr -
+ *	IPA_DMA_TASK_32B_ADDR command payload in H/W format.
+ * Used by clients using 32bit addresses. Used to perform DMA operation on
+ *  multiple descriptors.
+ *  The Opcode is dynamic, where it holds the number of buffer to process
+ * @sw_rsvd: Ignored by H/W. My be used by S/W
+ * @cmplt: Complete flag: When asserted IPA will interrupt SW when the entire
+ *  DMA related data was completely xfered to its destination.
+ * @eof: Enf Of Frame flag: When asserted IPA will assert the EOT to the
+ *  dest client. This is used used for aggr sequence
+ * @flsh: Flush flag: When asserted, pkt will go through the IPA blocks but
+ *  will not be xfered to dest client but rather will be discarded
+ * @lock: Lock endpoint flag: When asserted, IPA will stop processing
+ *  descriptors from other EPs in the same src grp (RX queue)
+ * @unlock: Unlock endpoint flag: When asserted, IPA will stop exclusively
+ *  servicing current EP out of the src EPs of the grp (RX queue)
+ * @size1: Size of buffer1 data
+ * @addr1: Pointer to buffer1 data
+ * @packet_size: Total packet size. If a pkt send using multiple DMA_TASKs,
+ *  only the first one needs to have this field set. It will be ignored
+ *  in subsequent DMA_TASKs until the packet ends (EOT). First DMA_TASK
+ *  must contain this field (2 or more buffers) or EOT.
+ */
+struct ipa_imm_cmd_hw_dma_task_32b_addr {
+	u16 sw_rsvd	: 11,
+	    cmplt	: 1,
+	    eof		: 1,
+	    flsh	: 1,
+	    lock	: 1,
+	    unlock	: 1;
+	u16 size1;
+	u32 addr1;
+	u16 packet_size;
+	u16 rsvd1;
+	u32 rsvd2;
+};
+
+/* IPA Status packet H/W structures and info */
+
+/* struct ipa_status_pkt_hw - IPA status packet payload in H/W format.
+ *  This structure describes the status packet H/W structure for the
+ *   following statuses: IPA_STATUS_PACKET, IPA_STATUS_DROPPED_PACKET,
+ *   IPA_STATUS_SUSPENDED_PACKET.
+ *  Other statuses types has different status packet structure.
+ * @status_opcode: The Type of the status (Opcode).
+ * @exception: (not bitmask) - the first exception that took place.
+ *  In case of exception, src endp and pkt len are always valid.
+ * @status_mask: Bit mask specifying on which H/W blocks the pkt was processed.
+ * @pkt_len: Pkt payload len including hdr, include retained hdr if used. Does
+ *  not include padding or checksum trailer len.
+ * @endp_src_idx: Source end point index.
+ * @rsvd1: reserved
+ * @endp_dest_idx: Destination end point index.
+ *  Not valid in case of exception
+ * @rsvd2: reserved
+ * @metadata: meta data value used by packet
+ * @flt_local: Filter table location flag: Does matching flt rule belongs to
+ *  flt tbl that resides in lcl memory? (if not, then system mem)
+ * @flt_hash: Filter hash hit flag: Does matching flt rule was in hash tbl?
+ * @flt_global: Global filter rule flag: Does matching flt rule belongs to
+ *  the global flt tbl? (if not, then the per endp tables)
+ * @flt_ret_hdr: Retain header in filter rule flag: Does matching flt rule
+ *  specifies to retain header?
+ * @flt_rule_id: The ID of the matching filter rule. This info can be combined
+ *  with endp_src_idx to locate the exact rule. ID=0x3ff reserved to specify
+ *  flt miss. In case of miss, all flt info to be ignored
+ * @rt_local: Route table location flag: Does matching rt rule belongs to
+ *  rt tbl that resides in lcl memory? (if not, then system mem)
+ * @rt_hash: Route hash hit flag: Does matching rt rule was in hash tbl?
+ * @ucp: UC Processing flag.
+ * @rt_tbl_idx: Index of rt tbl that contains the rule on which was a match
+ * @rt_rule_id: The ID of the matching rt rule. This info can be combined
+ *  with rt_tbl_idx to locate the exact rule. ID=0x3ff reserved to specify
+ *  rt miss. In case of miss, all rt info to be ignored
+ * @nat_hit: NAT hit flag: Was their NAT hit?
+ * @nat_entry_idx: Index of the NAT entry used of NAT processing
+ * @nat_type: Defines the type of the NAT operation (ignored for now)
+ * @tag_info: S/W defined value provided via immediate command
+ * @seq_num: Per source endp unique packet sequence number
+ * @time_of_day_ctr: running counter from IPA clock
+ * @hdr_local: Header table location flag: In header insertion, was the header
+ *  taken from the table resides in local memory? (If no, then system mem)
+ * @hdr_offset: Offset of used header in the header table
+ * @frag_hit: Frag hit flag: Was their frag rule hit in H/W frag table?
+ * @frag_rule: Frag rule index in H/W frag table in case of frag hit
+ * @hw_specific: H/W specific reserved value
+ */
+#define IPA_RULE_ID_BITS	10	/* See ipahal_is_rule_miss_id() */
+struct ipa_pkt_status_hw {
+	u8  status_opcode;
+	u8  exception;
+	u16 status_mask;
+	u16 pkt_len;
+	u8  endp_src_idx	: 5,
+	    rsvd1		: 3;
+	u8  endp_dest_idx	: 5,
+	    rsvd2		: 3;
+	u32 metadata;
+	u16 flt_local		: 1,
+	    flt_hash		: 1,
+	    flt_global		: 1,
+	    flt_ret_hdr		: 1,
+	    flt_rule_id		: IPA_RULE_ID_BITS,
+	    rt_local		: 1,
+	    rt_hash		: 1;
+	u16 ucp			: 1,
+	    rt_tbl_idx		: 5,
+	    rt_rule_id		: IPA_RULE_ID_BITS;
+	u64 nat_hit		: 1,
+	    nat_entry_idx	: 13,
+	    nat_type		: 2,
+	    tag_info		: 48;
+	u32 seq_num		: 8,
+	    time_of_day_ctr	: 24;
+	u16 hdr_local		: 1,
+	    hdr_offset		: 10,
+	    frag_hit		: 1,
+	    frag_rule		: 4;
+	u16 hw_specific;
+};
+
+void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset)
+{
+	struct ipa_imm_cmd_hw_dma_shared_mem *data;
+
+	ipa_assert(mem->size < 1 << 16);	/* size is 16 bits wide */
+	ipa_assert(offset < 1 << 16);		/* local_addr is 16 bits wide */
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->size = mem->size;
+	data->local_addr = offset;
+	data->direction = 0;	/* 0 = write to IPA; 1 = read from IPA */
+	data->skip_pipeline_clear = 0;
+	data->pipeline_clear_options = IPAHAL_HPS_CLEAR;
+	data->system_addr = mem->phys;
+
+	return data;
+}
+
+void *ipahal_hdr_init_local_pyld(struct ipa_dma_mem *mem, u32 offset)
+{
+	struct ipa_imm_cmd_hw_hdr_init_local *data;
+
+	ipa_assert(mem->size < 1 << 12);  /* size_hdr_table is 12 bits wide */
+	ipa_assert(offset < 1 << 16);		/* hdr_addr is 16 bits wide */
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->hdr_table_addr = mem->phys;
+	data->size_hdr_table = mem->size;
+	data->hdr_addr = offset;
+
+	return data;
+}
+
+static void *fltrt_init_common(struct ipa_dma_mem *mem, u32 hash_offset,
+			       u32 nhash_offset)
+{
+	struct ipa_imm_cmd_hw_ip_fltrt_init *data;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->hash_rules_addr = (u64)mem->phys;
+	data->hash_rules_size = (u32)mem->size;
+	data->hash_local_addr = hash_offset;
+	data->nhash_rules_addr = (u64)mem->phys;
+	data->nhash_rules_size = (u32)mem->size;
+	data->nhash_local_addr = nhash_offset;
+
+	return data;
+}
+
+void *ipahal_ip_v4_routing_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+			       u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_ip_v6_routing_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+				     u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_ip_v4_filter_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+				    u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_ip_v6_filter_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+				    u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_dma_task_32b_addr_pyld(struct ipa_dma_mem *mem)
+{
+	struct ipa_imm_cmd_hw_dma_task_32b_addr *data;
+
+	/* size1 and packet_size are both 16 bits wide */
+	ipa_assert(mem->size < 1 << 16);
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->cmplt = 0;
+	data->eof = 0;
+	data->flsh = 1;
+	data->lock = 0;
+	data->unlock = 0;
+	data->size1 = mem->size;
+	data->addr1 = mem->phys;
+	data->packet_size = mem->size;
+
+	return data;
+}
+
+void ipahal_payload_free(void *payload)
+{
+	kfree(payload);
+}
+
+/* IPA Packet Status Logic */
+
+/* Maps an exception type returned in a ipa_pkt_status_hw structure
+ * to the ipahal_pkt_status_exception value that represents it in
+ * the exception field of a ipahal_pkt_status structure.  Returns
+ * IPAHAL_PKT_STATUS_EXCEPTION_MAX for an unrecognized value.
+ */
+static enum ipahal_pkt_status_exception
+exception_map(u8 exception, bool is_ipv6)
+{
+	switch (exception) {
+	case 0x00:	return IPAHAL_PKT_STATUS_EXCEPTION_NONE;
+	case 0x01:	return IPAHAL_PKT_STATUS_EXCEPTION_DEAGGR;
+	case 0x04:	return IPAHAL_PKT_STATUS_EXCEPTION_IPTYPE;
+	case 0x08:	return IPAHAL_PKT_STATUS_EXCEPTION_PACKET_LENGTH;
+	case 0x10:	return IPAHAL_PKT_STATUS_EXCEPTION_FRAG_RULE_MISS;
+	case 0x20:	return IPAHAL_PKT_STATUS_EXCEPTION_SW_FILT;
+	case 0x40:	return is_ipv6 ? IPAHAL_PKT_STATUS_EXCEPTION_IPV6CT
+				       : IPAHAL_PKT_STATUS_EXCEPTION_NAT;
+	default:	return IPAHAL_PKT_STATUS_EXCEPTION_MAX;
+	}
+}
+
+/* ipahal_pkt_status_get_size() - Get H/W size of packet status */
+u32 ipahal_pkt_status_get_size(void)
+{
+	return sizeof(struct ipa_pkt_status_hw);
+}
+
+/* ipahal_pkt_status_parse() - Parse Packet Status payload to abstracted form
+ * @unparsed_status: Pointer to H/W format of the packet status as read from H/W
+ * @status: Pointer to pre-allocated buffer where the parsed info will be stored
+ */
+void ipahal_pkt_status_parse(const void *unparsed_status,
+			     struct ipahal_pkt_status *status)
+{
+	const struct ipa_pkt_status_hw *hw_status = unparsed_status;
+	bool is_ipv6;
+
+	status->status_opcode =
+			(enum ipahal_pkt_status_opcode)hw_status->status_opcode;
+	is_ipv6 = hw_status->status_mask & BIT(7) ? false : true;
+	/* If hardware status values change we may have to re-map this */
+	status->status_mask =
+			(enum ipahal_pkt_status_mask)hw_status->status_mask;
+	status->exception = exception_map(hw_status->exception, is_ipv6);
+	status->pkt_len = hw_status->pkt_len;
+	status->endp_src_idx = hw_status->endp_src_idx;
+	status->endp_dest_idx = hw_status->endp_dest_idx;
+	status->metadata = hw_status->metadata;
+	status->rt_miss = ipahal_is_rule_miss_id(hw_status->rt_rule_id);
+}
+
+int ipahal_init(void)
+{
+	struct ipa_dma_mem *mem = &ipahal_ctx->empty_fltrt_tbl;
+
+	/* Set up an empty filter/route table entry in system
+	 * memory.  This will be used, for example, to delete a
+	 * route safely.
+	 */
+	if (ipa_dma_alloc(mem, IPA_HW_TBL_WIDTH, GFP_KERNEL)) {
+		ipa_err("error allocating empty filter/route table\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void ipahal_exit(void)
+{
+	ipa_dma_free(&ipahal_ctx->empty_fltrt_tbl);
+}
+
+/* Does the given rule ID represent a routing or filter rule miss?
+ * A rule miss is indicated as an all-1's value in the rt_rule_id
+ * or flt_rule_id field of the ipahal_pkt_status structure.
+ */
+bool ipahal_is_rule_miss_id(u32 id)
+{
+	BUILD_BUG_ON(IPA_RULE_ID_BITS < 2);
+
+	return id == (1U << IPA_RULE_ID_BITS) - 1;
+}
+
+/**
+ * ipahal_rt_generate_empty_img() - Generate empty route table header
+ * @route_count:	Number of table entries
+ * @mem:		DMA memory object representing the header structure
+ *
+ * Allocates and fills an "empty" route table header having the given
+ * number of entries.  Each entry in the table contains the DMA address
+ * of a routing entry.
+ *
+ * This function initializes all entries to point at the preallocated
+ * empty routing entry in system RAM.
+ *
+ * Return:	0 if successful, or a negative error code otherwise
+ */
+int ipahal_rt_generate_empty_img(u32 route_count, struct ipa_dma_mem *mem)
+{
+	u64 addr;
+	int i;
+
+	BUILD_BUG_ON(!IPA_HW_TBL_HDR_WIDTH);
+
+	if (ipa_dma_alloc(mem, route_count * IPA_HW_TBL_HDR_WIDTH, GFP_KERNEL))
+		return -ENOMEM;
+
+	addr = (u64)ipahal_ctx->empty_fltrt_tbl.phys;
+	for (i = 0; i < route_count; i++)
+		put_unaligned(addr, mem->virt + i * IPA_HW_TBL_HDR_WIDTH);
+
+	return 0;
+}
+
+/**
+ * ipahal_flt_generate_empty_img() - Generate empty filter table header
+ * @filter_bitmap:	Bitmap representing which endpoints support filtering
+ * @mem:		DMA memory object representing the header structure
+ *
+ * Allocates and fills an "empty" filter table header based on the
+ * given filter bitmap.
+ *
+ * The first slot in a filter table header is a 64-bit bitmap whose
+ * set bits define which endpoints support filtering.  Following
+ * this, each set bit in the mask has the DMA address of the filter
+ * used for the corresponding endpoint.
+ *
+ * This function initializes all endpoints that support filtering to
+ * point at the preallocated empty filter in system RAM.
+ *
+ * Note:  the (software) bitmap here uses bit 0 to represent
+ * endpoint 0, bit 1 for endpoint 1, and so on.  This is different
+ * from the hardware (which uses bit 1 to represent filter 0, etc.).
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+int ipahal_flt_generate_empty_img(u64 filter_bitmap, struct ipa_dma_mem *mem)
+{
+	u32 filter_count = hweight32(filter_bitmap) + 1;
+	u64 addr;
+	int i;
+
+	ipa_assert(filter_bitmap);
+
+	if (ipa_dma_alloc(mem, filter_count * IPA_HW_TBL_HDR_WIDTH, GFP_KERNEL))
+		return -ENOMEM;
+
+	/* Save the endpoint bitmap in the first slot of the table.
+	 * Convert it from software to hardware representation by
+	 * shifting it left one position.
+	 * XXX Does bit position 0 represent global?  At IPA3, global
+	 * XXX configuration is possible but not used.
+	 */
+	put_unaligned(filter_bitmap << 1, mem->virt);
+
+	/* Point every entry in the table at the empty filter */
+	addr = (u64)ipahal_ctx->empty_fltrt_tbl.phys;
+	for (i = 1; i < filter_count; i++)
+		put_unaligned(addr, mem->virt + i * IPA_HW_TBL_HDR_WIDTH);
+
+	return 0;
+}
+
+void ipahal_free_empty_img(struct ipa_dma_mem *mem)
+{
+	ipa_dma_free(mem);
+}
diff --git a/drivers/net/ipa/ipahal.h b/drivers/net/ipa/ipahal.h
new file mode 100644
index 000000000000..940254940d90
--- /dev/null
+++ b/drivers/net/ipa/ipahal.h
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPAHAL_H_
+#define _IPAHAL_H_
+
+#include <linux/types.h>
+
+#include "ipa_dma.h"
+
+/* The IPA implements offloaded packet filtering and routing
+ * capabilities.  This is managed by programming IPA-resident
+ * tables of rules that define the processing that should be
+ * performed by the IPA and the conditions under which they
+ * should be applied.  Aspects of these rules are constrained
+ * by things like table entry sizes and alignment requirements;
+ * all of these are in units of bytes.  These definitions are
+ * subject to some constraints:
+ * - IPA_HW_TBL_WIDTH must be non-zero
+ * - IPA_HW_TBL_SYSADDR_ALIGN must be a non-zero power of 2
+ * - IPA_HW_TBL_HDR_WIDTH must be non-zero
+ *
+ * Values could differ for different versions of IPA hardware.
+ * These values are for v3.5.1, found in the SDM845.
+ */
+#define IPA_HW_TBL_WIDTH		8
+#define IPA_HW_TBL_SYSADDR_ALIGN	128
+#define IPA_HW_TBL_HDR_WIDTH		8
+
+/**
+ * ipahal_dma_shared_mem_write_pyld() - Write to shared memory command payload
+ *
+ * Return a pointer to the payload for a DMA shared memory write immediate
+ * command, or null if one can't be allocated.  Result is dynamically
+ * allocated, and caller must ensure it gets released by providing it to
+ * ipahal_destroy_imm_cmd() when it is no longer needed.
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset);
+
+/**
+ * ipahal_hdr_init_local_pyld() - Header initialization command payload
+ * mem:		DMA buffer containing data for initialization
+ * offset:	Where in location IPA local memory to write
+ *
+ * Return a pointer to the payload for a header init local immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_hdr_init_local_pyld(struct ipa_dma_mem *mem, u32 offset);
+
+/**
+ * ipahal_ip_v4_routing_init_pyld() - IPv4 routing table initialization payload
+ * mem:		The IPv4 routing table data to be written
+ * hash_offset:	The location in IPA memory for a hashed routing table
+ * nhash_offset: The location in IPA memory for a non-hashed routing table
+ *
+ * Return a pointer to the payload for an IPv4 routing init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v4_routing_init_pyld(struct ipa_dma_mem *mem,
+				     u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_ip_v6_routing_init_pyld() - IPv6 routing table initialization payload
+ * mem:		The IPv6 routing table data to be written
+ * hash_offset:	The location in IPA memory for a hashed routing table
+ * nhash_offset: The location in IPA memory for a non-hashed routing table
+ *
+ * Return a pointer to the payload for an IPv4 routing init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v6_routing_init_pyld(struct ipa_dma_mem *mem,
+				     u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_ip_v4_filter_init_pyld() - IPv4 filter table initialization payload
+ * mem:		The IPv4 filter table data to be written
+ * hash_offset:	The location in IPA memory for a hashed filter table
+ * nhash_offset: The location in IPA memory for a non-hashed filter table
+ *
+ * Return a pointer to the payload for an IPv4 filter init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v4_filter_init_pyld(struct ipa_dma_mem *mem,
+				    u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_ip_v6_filter_init_pyld() - IPv6 filter table initialization payload
+ * mem:		The IPv6 filter table data to be written
+ * hash_offset:	The location in IPA memory for a hashed filter table
+ * nhash_offset: The location in IPA memory for a non-hashed filter table
+ *
+ * Return a pointer to the payload for an IPv4 filter init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v6_filter_init_pyld(struct ipa_dma_mem *mem,
+				    u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_dma_task_32b_addr_pyld() - 32-bit DMA task command payload
+ * mem:		DMA memory involved in the task
+ *
+ * Return a pointer to the payload for DMA task 32-bit address immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ */
+void *ipahal_dma_task_32b_addr_pyld(struct ipa_dma_mem *mem);
+
+/**
+ * ipahal_payload_free() - Release an allocated immediate command payload
+ * @payload:	Payload to be released
+ */
+void ipahal_payload_free(void *payload);
+
+/**
+ * enum ipahal_pkt_status_opcode - Packet Status Opcode
+ * @IPAHAL_STATUS_OPCODE_PACKET_2ND_PASS: Packet Status generated as part of
+ *  IPA second processing pass for a packet (i.e. IPA XLAT processing for
+ *  the translated packet).
+ *
+ *  The values assigned here are assumed by ipa_pkt_status_parse()
+ *  to match values returned in the status_opcode field of a
+ *  ipa_pkt_status_hw structure inserted by the IPA in received
+ *  buffer.
+ */
+enum ipahal_pkt_status_opcode {
+	IPAHAL_PKT_STATUS_OPCODE_PACKET			= 0x01,
+	IPAHAL_PKT_STATUS_OPCODE_NEW_FRAG_RULE		= 0x02,
+	IPAHAL_PKT_STATUS_OPCODE_DROPPED_PACKET		= 0x04,
+	IPAHAL_PKT_STATUS_OPCODE_SUSPENDED_PACKET	= 0x08,
+	IPAHAL_PKT_STATUS_OPCODE_LOG			= 0x10,
+	IPAHAL_PKT_STATUS_OPCODE_DCMP			= 0x20,
+	IPAHAL_PKT_STATUS_OPCODE_PACKET_2ND_PASS	= 0x40,
+};
+
+/**
+ * enum ipahal_pkt_status_exception - Packet Status exception type
+ * @IPAHAL_PKT_STATUS_EXCEPTION_PACKET_LENGTH: formerly IHL exception.
+ *
+ * Note: IPTYPE, PACKET_LENGTH and PACKET_THRESHOLD exceptions means that
+ *  partial / no IP processing took place and corresponding Status Mask
+ *  fields should be ignored. Flt and rt info is not valid.
+ *
+ * NOTE:: Any change to this enum, need to change to
+ *	ipahal_pkt_status_exception_to_str array as well.
+ */
+enum ipahal_pkt_status_exception {
+	IPAHAL_PKT_STATUS_EXCEPTION_NONE = 0,
+	IPAHAL_PKT_STATUS_EXCEPTION_DEAGGR,
+	IPAHAL_PKT_STATUS_EXCEPTION_IPTYPE,
+	IPAHAL_PKT_STATUS_EXCEPTION_PACKET_LENGTH,
+	IPAHAL_PKT_STATUS_EXCEPTION_PACKET_THRESHOLD,
+	IPAHAL_PKT_STATUS_EXCEPTION_FRAG_RULE_MISS,
+	IPAHAL_PKT_STATUS_EXCEPTION_SW_FILT,
+	/* NAT and IPv6CT have the same value at HW.
+	 * NAT for IPv4 and IPv6CT for IPv6 exceptions
+	 */
+	IPAHAL_PKT_STATUS_EXCEPTION_NAT,
+	IPAHAL_PKT_STATUS_EXCEPTION_IPV6CT,
+	IPAHAL_PKT_STATUS_EXCEPTION_MAX,
+};
+
+/**
+ * enum ipahal_pkt_status_mask - Packet Status bitmask values of
+ *  the contained flags. This bitmask indicates flags on the properties of
+ *  the packet as well as IPA processing it may had.
+ * @TAG_VALID: Flag specifying if TAG and TAG info valid?
+ * @CKSUM_PROCESS: CSUM block processing flag: Was pkt processed by csum block?
+ *  If so, csum trailer exists
+ */
+enum ipahal_pkt_status_mask {
+	/* Other values are defined but are not specifically handled yet. */
+	IPAHAL_PKT_STATUS_MASK_CKSUM_PROCESS	= 0x0100,
+};
+
+/**
+ * struct ipahal_pkt_status - IPA status packet abstracted payload.
+ * @status_opcode: The type of status (Opcode).
+ * @exception: The first exception that took place.
+ *  In case of exception, endp_src_idx and pkt_len are always valid.
+ * @status_mask: Bit mask for flags on several properties on the packet
+ *  and processing it may passed at IPA.
+ * @pkt_len: Pkt pyld len including hdr and retained hdr if used. Does
+ *  not include padding or checksum trailer len.
+ * @endp_src_idx: Source end point index.
+ * @endp_dest_idx: Destination end point index.
+ *  Not valid in case of exception
+ * @metadata: meta data value used by packet
+ * @rt_miss: Routing miss flag: Was their a routing rule miss?
+ *
+ * This structure describes the status packet fields for the following
+ * status values: IPA_STATUS_PACKET, IPA_STATUS_DROPPED_PACKET,
+ * IPA_STATUS_SUSPENDED_PACKET.  Other status types have different status
+ * packet structure.  Note that the hardware supplies additional status
+ * information that is currently unused.
+ */
+struct ipahal_pkt_status {
+	enum ipahal_pkt_status_opcode status_opcode;
+	enum ipahal_pkt_status_exception exception;
+	enum ipahal_pkt_status_mask status_mask;
+	u32 pkt_len;
+	u8 endp_src_idx;
+	u8 endp_dest_idx;
+	u32 metadata;
+	bool rt_miss;
+};
+
+/**
+ * ipahal_pkt_status_get_size() - Get size of a hardware packet status
+ */
+u32 ipahal_pkt_status_get_size(void);
+
+/* ipahal_pkt_status_parse() - Parse packet status payload
+ * @unparsed_status:	Packet status read from hardware
+ * @status:		Buffer to hold parsed status information
+ */
+void ipahal_pkt_status_parse(const void *unparsed_status,
+			     struct ipahal_pkt_status *status);
+
+int ipahal_init(void);
+void ipahal_exit(void);
+
+/* Does the given ID represent rule miss? */
+bool ipahal_is_rule_miss_id(u32 id);
+
+int ipahal_rt_generate_empty_img(u32 route_count, struct ipa_dma_mem *mem);
+int ipahal_flt_generate_empty_img(u64 ep_bitmap, struct ipa_dma_mem *mem);
+
+/**
+ * ipahal_free_empty_img() - Free empty filter or route image
+ * @mem:	DMA memory containing filter/route data
+ */
+void ipahal_free_empty_img(struct ipa_dma_mem *mem);
+
+#endif /* _IPAHAL_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 04/12] soc: qcom: ipa: immediate commands
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch contains (mostly) code implementing "immediate commands."
(The source files are still named "ipahal" for historical reasons.)

One channel (APPS CMD_PROD) is used for sending commands *to* the
IPA itself, rather than passing data through it.  These immediate
commands are issued to the IPA using the normal GSI queueing
mechanism.  And each command's completion is handled using the
normal GSI transfer completion mechanisms.

In addition to immediate commands, the "IPA HAL" includes code for
interpreting status packets that are supplied to the IPA on consumer
channels.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipahal.c | 541 +++++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipahal.h | 253 ++++++++++++++++++
 2 files changed, 794 insertions(+)
 create mode 100644 drivers/net/ipa/ipahal.c
 create mode 100644 drivers/net/ipa/ipahal.h

diff --git a/drivers/net/ipa/ipahal.c b/drivers/net/ipa/ipahal.c
new file mode 100644
index 000000000000..de00bcd54d4f
--- /dev/null
+++ b/drivers/net/ipa/ipahal.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include "ipahal.h"
+#include "ipa_i.h"	/* ipa_err() */
+#include "ipa_dma.h"
+
+/**
+ * DOC:  IPA Immediate Commands
+ *
+ * The APPS_CMD_PROD channel is used to issue immediate commands to
+ * the IPA.  An immediate command is generally used to request the
+ * IPA do something other than data transfer.
+ *
+ * An immediate command is represented by a GSI transfer element.
+ * Each immediate command has a well-defined format, with a known
+ * length.  The transfer element's length field can therefore be
+ * used to hold a command's opcode.  The "payload" of an immediate
+ * command contains additional information required for the command.
+ * It resides in DRAM and is referred to using the DMA memory data
+ * pointer (the same one used to refer to the data in a "normal"
+ * transfer).
+ *
+ * Immediate commands are issued to the IPA through the APPS_CMD_PROD
+ * channel using the normal GSI queueing mechanism.  And each command's
+ * completion is handled using the normal GSI transfer completion
+ * mechanisms.
+ */
+
+/**
+ * struct ipahal_context - HAL global context data
+ * @empty_fltrt_tbl:	Empty table to be used for table initialization
+ */
+static struct ipahal_context {
+	struct ipa_dma_mem empty_fltrt_tbl;
+} ipahal_ctx_struct;
+static struct ipahal_context *ipahal_ctx = &ipahal_ctx_struct;
+
+/* enum ipa_pipeline_clear_option - Values for pipeline clear waiting options
+ * @IPAHAL_HPS_CLEAR: Wait for HPS clear. All queues except high priority queue
+ *  shall not be serviced until HPS is clear of packets or immediate commands.
+ *  The high priority Rx queue / Q6ZIP group shall still be serviced normally.
+ *
+ * @IPAHAL_SRC_GRP_CLEAR: Wait for originating source group to be clear
+ *  (for no packet contexts allocated to the originating source group).
+ *  The source group / Rx queue shall not be serviced until all previously
+ *  allocated packet contexts are released. All other source groups/queues shall
+ *  be serviced normally.
+ *
+ * @IPAHAL_FULL_PIPELINE_CLEAR: Wait for full pipeline to be clear.
+ *  All groups / Rx queues shall not be serviced until IPA pipeline is fully
+ *  clear. This should be used for debug only.
+ *
+ *  The values assigned to these are assumed by the REGISTER_WRITE
+ *  (struct ipa_imm_cmd_hw_register_write) and the DMA_SHARED_MEM
+ *  (struct ipa_imm_cmd_hw_dma_shared_mem) immediate commands for
+ *  IPA version 3 hardware.  They are also used to modify the opcode
+ *  used to implement these commands for IPA version 4 hardware.
+ */
+enum ipahal_pipeline_clear_option {
+	IPAHAL_HPS_CLEAR		= 0,
+	IPAHAL_SRC_GRP_CLEAR		= 1,
+	IPAHAL_FULL_PIPELINE_CLEAR	= 2,
+};
+
+/* Immediate commands H/W structures */
+
+/* struct ipa_imm_cmd_hw_ip_fltrt_init - IP_V*_FILTER_INIT/IP_V*_ROUTING_INIT
+ * command payload in H/W format.
+ * Inits IPv4/v6 routing or filter block.
+ * @hash_rules_addr: Addr in system mem where hashable flt/rt rules starts
+ * @hash_rules_size: Size in bytes of the hashable tbl to cpy to local mem
+ * @hash_local_addr: Addr in shared mem where hashable flt/rt tbl should
+ *  be copied to
+ * @nhash_rules_size: Size in bytes of the non-hashable tbl to cpy to local mem
+ * @nhash_local_addr: Addr in shared mem where non-hashable flt/rt tbl should
+ *  be copied to
+ * @rsvd: reserved
+ * @nhash_rules_addr: Addr in sys mem where non-hashable flt/rt tbl starts
+ */
+struct ipa_imm_cmd_hw_ip_fltrt_init {
+	u64 hash_rules_addr;
+	u64 hash_rules_size	: 12,
+	    hash_local_addr	: 16,
+	    nhash_rules_size	: 12,
+	    nhash_local_addr	: 16,
+	    rsvd		: 8;
+	u64 nhash_rules_addr;
+};
+
+/* struct ipa_imm_cmd_hw_hdr_init_local - HDR_INIT_LOCAL command payload
+ *  in H/W format.
+ * Inits hdr table within local mem with the hdrs and their length.
+ * @hdr_table_addr: Word address in sys mem where the table starts (SRC)
+ * @size_hdr_table: Size of the above (in bytes)
+ * @hdr_addr: header address in IPA sram (used as DST for memory copy)
+ * @rsvd: reserved
+ */
+struct ipa_imm_cmd_hw_hdr_init_local {
+	u64 hdr_table_addr;
+	u32 size_hdr_table	: 12,
+	    hdr_addr		: 16,
+	    rsvd		: 4;
+};
+
+/* struct ipa_imm_cmd_hw_dma_shared_mem - DMA_SHARED_MEM command payload
+ *  in H/W format.
+ * Perform mem copy into or out of the SW area of IPA local mem
+ * @sw_rsvd: Ignored by H/W. My be used by S/W
+ * @size: Size in bytes of data to copy. Expected size is up to 2K bytes
+ * @local_addr: Address in IPA local memory
+ * @direction: Read or write?
+ *	0: IPA write, Write to local address from system address
+ *	1: IPA read, Read from local address to system address
+ * @skip_pipeline_clear: 0 to wait until IPA pipeline is clear. 1 don't wait
+ * @pipeline_clear_options: options for pipeline to clear
+ *	0: HPS - no pkt inside HPS (not grp specific)
+ *	1: source group - The immediate cmd src grp does npt use any pkt ctxs
+ *	2: Wait until no pkt reside inside IPA pipeline
+ *	3: reserved
+ * @rsvd: reserved - should be set to zero
+ * @system_addr: Address in system memory
+ */
+struct ipa_imm_cmd_hw_dma_shared_mem {
+	u16 sw_rsvd;
+	u16 size;
+	u16 local_addr;
+	u16 direction			: 1,
+	    skip_pipeline_clear		: 1,
+	    pipeline_clear_options	: 2,
+	    rsvd			: 12;
+	u64 system_addr;
+};
+
+/* struct ipa_imm_cmd_hw_dma_task_32b_addr -
+ *	IPA_DMA_TASK_32B_ADDR command payload in H/W format.
+ * Used by clients using 32bit addresses. Used to perform DMA operation on
+ *  multiple descriptors.
+ *  The Opcode is dynamic, where it holds the number of buffer to process
+ * @sw_rsvd: Ignored by H/W. My be used by S/W
+ * @cmplt: Complete flag: When asserted IPA will interrupt SW when the entire
+ *  DMA related data was completely xfered to its destination.
+ * @eof: Enf Of Frame flag: When asserted IPA will assert the EOT to the
+ *  dest client. This is used used for aggr sequence
+ * @flsh: Flush flag: When asserted, pkt will go through the IPA blocks but
+ *  will not be xfered to dest client but rather will be discarded
+ * @lock: Lock endpoint flag: When asserted, IPA will stop processing
+ *  descriptors from other EPs in the same src grp (RX queue)
+ * @unlock: Unlock endpoint flag: When asserted, IPA will stop exclusively
+ *  servicing current EP out of the src EPs of the grp (RX queue)
+ * @size1: Size of buffer1 data
+ * @addr1: Pointer to buffer1 data
+ * @packet_size: Total packet size. If a pkt send using multiple DMA_TASKs,
+ *  only the first one needs to have this field set. It will be ignored
+ *  in subsequent DMA_TASKs until the packet ends (EOT). First DMA_TASK
+ *  must contain this field (2 or more buffers) or EOT.
+ */
+struct ipa_imm_cmd_hw_dma_task_32b_addr {
+	u16 sw_rsvd	: 11,
+	    cmplt	: 1,
+	    eof		: 1,
+	    flsh	: 1,
+	    lock	: 1,
+	    unlock	: 1;
+	u16 size1;
+	u32 addr1;
+	u16 packet_size;
+	u16 rsvd1;
+	u32 rsvd2;
+};
+
+/* IPA Status packet H/W structures and info */
+
+/* struct ipa_status_pkt_hw - IPA status packet payload in H/W format.
+ *  This structure describes the status packet H/W structure for the
+ *   following statuses: IPA_STATUS_PACKET, IPA_STATUS_DROPPED_PACKET,
+ *   IPA_STATUS_SUSPENDED_PACKET.
+ *  Other statuses types has different status packet structure.
+ * @status_opcode: The Type of the status (Opcode).
+ * @exception: (not bitmask) - the first exception that took place.
+ *  In case of exception, src endp and pkt len are always valid.
+ * @status_mask: Bit mask specifying on which H/W blocks the pkt was processed.
+ * @pkt_len: Pkt payload len including hdr, include retained hdr if used. Does
+ *  not include padding or checksum trailer len.
+ * @endp_src_idx: Source end point index.
+ * @rsvd1: reserved
+ * @endp_dest_idx: Destination end point index.
+ *  Not valid in case of exception
+ * @rsvd2: reserved
+ * @metadata: meta data value used by packet
+ * @flt_local: Filter table location flag: Does matching flt rule belongs to
+ *  flt tbl that resides in lcl memory? (if not, then system mem)
+ * @flt_hash: Filter hash hit flag: Does matching flt rule was in hash tbl?
+ * @flt_global: Global filter rule flag: Does matching flt rule belongs to
+ *  the global flt tbl? (if not, then the per endp tables)
+ * @flt_ret_hdr: Retain header in filter rule flag: Does matching flt rule
+ *  specifies to retain header?
+ * @flt_rule_id: The ID of the matching filter rule. This info can be combined
+ *  with endp_src_idx to locate the exact rule. ID=0x3ff reserved to specify
+ *  flt miss. In case of miss, all flt info to be ignored
+ * @rt_local: Route table location flag: Does matching rt rule belongs to
+ *  rt tbl that resides in lcl memory? (if not, then system mem)
+ * @rt_hash: Route hash hit flag: Does matching rt rule was in hash tbl?
+ * @ucp: UC Processing flag.
+ * @rt_tbl_idx: Index of rt tbl that contains the rule on which was a match
+ * @rt_rule_id: The ID of the matching rt rule. This info can be combined
+ *  with rt_tbl_idx to locate the exact rule. ID=0x3ff reserved to specify
+ *  rt miss. In case of miss, all rt info to be ignored
+ * @nat_hit: NAT hit flag: Was their NAT hit?
+ * @nat_entry_idx: Index of the NAT entry used of NAT processing
+ * @nat_type: Defines the type of the NAT operation (ignored for now)
+ * @tag_info: S/W defined value provided via immediate command
+ * @seq_num: Per source endp unique packet sequence number
+ * @time_of_day_ctr: running counter from IPA clock
+ * @hdr_local: Header table location flag: In header insertion, was the header
+ *  taken from the table resides in local memory? (If no, then system mem)
+ * @hdr_offset: Offset of used header in the header table
+ * @frag_hit: Frag hit flag: Was their frag rule hit in H/W frag table?
+ * @frag_rule: Frag rule index in H/W frag table in case of frag hit
+ * @hw_specific: H/W specific reserved value
+ */
+#define IPA_RULE_ID_BITS	10	/* See ipahal_is_rule_miss_id() */
+struct ipa_pkt_status_hw {
+	u8  status_opcode;
+	u8  exception;
+	u16 status_mask;
+	u16 pkt_len;
+	u8  endp_src_idx	: 5,
+	    rsvd1		: 3;
+	u8  endp_dest_idx	: 5,
+	    rsvd2		: 3;
+	u32 metadata;
+	u16 flt_local		: 1,
+	    flt_hash		: 1,
+	    flt_global		: 1,
+	    flt_ret_hdr		: 1,
+	    flt_rule_id		: IPA_RULE_ID_BITS,
+	    rt_local		: 1,
+	    rt_hash		: 1;
+	u16 ucp			: 1,
+	    rt_tbl_idx		: 5,
+	    rt_rule_id		: IPA_RULE_ID_BITS;
+	u64 nat_hit		: 1,
+	    nat_entry_idx	: 13,
+	    nat_type		: 2,
+	    tag_info		: 48;
+	u32 seq_num		: 8,
+	    time_of_day_ctr	: 24;
+	u16 hdr_local		: 1,
+	    hdr_offset		: 10,
+	    frag_hit		: 1,
+	    frag_rule		: 4;
+	u16 hw_specific;
+};
+
+void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset)
+{
+	struct ipa_imm_cmd_hw_dma_shared_mem *data;
+
+	ipa_assert(mem->size < 1 << 16);	/* size is 16 bits wide */
+	ipa_assert(offset < 1 << 16);		/* local_addr is 16 bits wide */
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->size = mem->size;
+	data->local_addr = offset;
+	data->direction = 0;	/* 0 = write to IPA; 1 = read from IPA */
+	data->skip_pipeline_clear = 0;
+	data->pipeline_clear_options = IPAHAL_HPS_CLEAR;
+	data->system_addr = mem->phys;
+
+	return data;
+}
+
+void *ipahal_hdr_init_local_pyld(struct ipa_dma_mem *mem, u32 offset)
+{
+	struct ipa_imm_cmd_hw_hdr_init_local *data;
+
+	ipa_assert(mem->size < 1 << 12);  /* size_hdr_table is 12 bits wide */
+	ipa_assert(offset < 1 << 16);		/* hdr_addr is 16 bits wide */
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->hdr_table_addr = mem->phys;
+	data->size_hdr_table = mem->size;
+	data->hdr_addr = offset;
+
+	return data;
+}
+
+static void *fltrt_init_common(struct ipa_dma_mem *mem, u32 hash_offset,
+			       u32 nhash_offset)
+{
+	struct ipa_imm_cmd_hw_ip_fltrt_init *data;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->hash_rules_addr = (u64)mem->phys;
+	data->hash_rules_size = (u32)mem->size;
+	data->hash_local_addr = hash_offset;
+	data->nhash_rules_addr = (u64)mem->phys;
+	data->nhash_rules_size = (u32)mem->size;
+	data->nhash_local_addr = nhash_offset;
+
+	return data;
+}
+
+void *ipahal_ip_v4_routing_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+			       u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_ip_v6_routing_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+				     u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_ip_v4_filter_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+				    u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_ip_v6_filter_init_pyld(struct ipa_dma_mem *mem, u32 hash_offset,
+				    u32 nhash_offset)
+{
+	return fltrt_init_common(mem, hash_offset, nhash_offset);
+}
+
+void *ipahal_dma_task_32b_addr_pyld(struct ipa_dma_mem *mem)
+{
+	struct ipa_imm_cmd_hw_dma_task_32b_addr *data;
+
+	/* size1 and packet_size are both 16 bits wide */
+	ipa_assert(mem->size < 1 << 16);
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->cmplt = 0;
+	data->eof = 0;
+	data->flsh = 1;
+	data->lock = 0;
+	data->unlock = 0;
+	data->size1 = mem->size;
+	data->addr1 = mem->phys;
+	data->packet_size = mem->size;
+
+	return data;
+}
+
+void ipahal_payload_free(void *payload)
+{
+	kfree(payload);
+}
+
+/* IPA Packet Status Logic */
+
+/* Maps an exception type returned in a ipa_pkt_status_hw structure
+ * to the ipahal_pkt_status_exception value that represents it in
+ * the exception field of a ipahal_pkt_status structure.  Returns
+ * IPAHAL_PKT_STATUS_EXCEPTION_MAX for an unrecognized value.
+ */
+static enum ipahal_pkt_status_exception
+exception_map(u8 exception, bool is_ipv6)
+{
+	switch (exception) {
+	case 0x00:	return IPAHAL_PKT_STATUS_EXCEPTION_NONE;
+	case 0x01:	return IPAHAL_PKT_STATUS_EXCEPTION_DEAGGR;
+	case 0x04:	return IPAHAL_PKT_STATUS_EXCEPTION_IPTYPE;
+	case 0x08:	return IPAHAL_PKT_STATUS_EXCEPTION_PACKET_LENGTH;
+	case 0x10:	return IPAHAL_PKT_STATUS_EXCEPTION_FRAG_RULE_MISS;
+	case 0x20:	return IPAHAL_PKT_STATUS_EXCEPTION_SW_FILT;
+	case 0x40:	return is_ipv6 ? IPAHAL_PKT_STATUS_EXCEPTION_IPV6CT
+				       : IPAHAL_PKT_STATUS_EXCEPTION_NAT;
+	default:	return IPAHAL_PKT_STATUS_EXCEPTION_MAX;
+	}
+}
+
+/* ipahal_pkt_status_get_size() - Get H/W size of packet status */
+u32 ipahal_pkt_status_get_size(void)
+{
+	return sizeof(struct ipa_pkt_status_hw);
+}
+
+/* ipahal_pkt_status_parse() - Parse Packet Status payload to abstracted form
+ * @unparsed_status: Pointer to H/W format of the packet status as read from H/W
+ * @status: Pointer to pre-allocated buffer where the parsed info will be stored
+ */
+void ipahal_pkt_status_parse(const void *unparsed_status,
+			     struct ipahal_pkt_status *status)
+{
+	const struct ipa_pkt_status_hw *hw_status = unparsed_status;
+	bool is_ipv6;
+
+	status->status_opcode =
+			(enum ipahal_pkt_status_opcode)hw_status->status_opcode;
+	is_ipv6 = hw_status->status_mask & BIT(7) ? false : true;
+	/* If hardware status values change we may have to re-map this */
+	status->status_mask =
+			(enum ipahal_pkt_status_mask)hw_status->status_mask;
+	status->exception = exception_map(hw_status->exception, is_ipv6);
+	status->pkt_len = hw_status->pkt_len;
+	status->endp_src_idx = hw_status->endp_src_idx;
+	status->endp_dest_idx = hw_status->endp_dest_idx;
+	status->metadata = hw_status->metadata;
+	status->rt_miss = ipahal_is_rule_miss_id(hw_status->rt_rule_id);
+}
+
+int ipahal_init(void)
+{
+	struct ipa_dma_mem *mem = &ipahal_ctx->empty_fltrt_tbl;
+
+	/* Set up an empty filter/route table entry in system
+	 * memory.  This will be used, for example, to delete a
+	 * route safely.
+	 */
+	if (ipa_dma_alloc(mem, IPA_HW_TBL_WIDTH, GFP_KERNEL)) {
+		ipa_err("error allocating empty filter/route table\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void ipahal_exit(void)
+{
+	ipa_dma_free(&ipahal_ctx->empty_fltrt_tbl);
+}
+
+/* Does the given rule ID represent a routing or filter rule miss?
+ * A rule miss is indicated as an all-1's value in the rt_rule_id
+ * or flt_rule_id field of the ipahal_pkt_status structure.
+ */
+bool ipahal_is_rule_miss_id(u32 id)
+{
+	BUILD_BUG_ON(IPA_RULE_ID_BITS < 2);
+
+	return id == (1U << IPA_RULE_ID_BITS) - 1;
+}
+
+/**
+ * ipahal_rt_generate_empty_img() - Generate empty route table header
+ * @route_count:	Number of table entries
+ * @mem:		DMA memory object representing the header structure
+ *
+ * Allocates and fills an "empty" route table header having the given
+ * number of entries.  Each entry in the table contains the DMA address
+ * of a routing entry.
+ *
+ * This function initializes all entries to point@the preallocated
+ * empty routing entry in system RAM.
+ *
+ * Return:	0 if successful, or a negative error code otherwise
+ */
+int ipahal_rt_generate_empty_img(u32 route_count, struct ipa_dma_mem *mem)
+{
+	u64 addr;
+	int i;
+
+	BUILD_BUG_ON(!IPA_HW_TBL_HDR_WIDTH);
+
+	if (ipa_dma_alloc(mem, route_count * IPA_HW_TBL_HDR_WIDTH, GFP_KERNEL))
+		return -ENOMEM;
+
+	addr = (u64)ipahal_ctx->empty_fltrt_tbl.phys;
+	for (i = 0; i < route_count; i++)
+		put_unaligned(addr, mem->virt + i * IPA_HW_TBL_HDR_WIDTH);
+
+	return 0;
+}
+
+/**
+ * ipahal_flt_generate_empty_img() - Generate empty filter table header
+ * @filter_bitmap:	Bitmap representing which endpoints support filtering
+ * @mem:		DMA memory object representing the header structure
+ *
+ * Allocates and fills an "empty" filter table header based on the
+ * given filter bitmap.
+ *
+ * The first slot in a filter table header is a 64-bit bitmap whose
+ * set bits define which endpoints support filtering.  Following
+ * this, each set bit in the mask has the DMA address of the filter
+ * used for the corresponding endpoint.
+ *
+ * This function initializes all endpoints that support filtering to
+ * point at the preallocated empty filter in system RAM.
+ *
+ * Note:  the (software) bitmap here uses bit 0 to represent
+ * endpoint 0, bit 1 for endpoint 1, and so on.  This is different
+ * from the hardware (which uses bit 1 to represent filter 0, etc.).
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+int ipahal_flt_generate_empty_img(u64 filter_bitmap, struct ipa_dma_mem *mem)
+{
+	u32 filter_count = hweight32(filter_bitmap) + 1;
+	u64 addr;
+	int i;
+
+	ipa_assert(filter_bitmap);
+
+	if (ipa_dma_alloc(mem, filter_count * IPA_HW_TBL_HDR_WIDTH, GFP_KERNEL))
+		return -ENOMEM;
+
+	/* Save the endpoint bitmap in the first slot of the table.
+	 * Convert it from software to hardware representation by
+	 * shifting it left one position.
+	 * XXX Does bit position 0 represent global?  At IPA3, global
+	 * XXX configuration is possible but not used.
+	 */
+	put_unaligned(filter_bitmap << 1, mem->virt);
+
+	/* Point every entry in the table at the empty filter */
+	addr = (u64)ipahal_ctx->empty_fltrt_tbl.phys;
+	for (i = 1; i < filter_count; i++)
+		put_unaligned(addr, mem->virt + i * IPA_HW_TBL_HDR_WIDTH);
+
+	return 0;
+}
+
+void ipahal_free_empty_img(struct ipa_dma_mem *mem)
+{
+	ipa_dma_free(mem);
+}
diff --git a/drivers/net/ipa/ipahal.h b/drivers/net/ipa/ipahal.h
new file mode 100644
index 000000000000..940254940d90
--- /dev/null
+++ b/drivers/net/ipa/ipahal.h
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPAHAL_H_
+#define _IPAHAL_H_
+
+#include <linux/types.h>
+
+#include "ipa_dma.h"
+
+/* The IPA implements offloaded packet filtering and routing
+ * capabilities.  This is managed by programming IPA-resident
+ * tables of rules that define the processing that should be
+ * performed by the IPA and the conditions under which they
+ * should be applied.  Aspects of these rules are constrained
+ * by things like table entry sizes and alignment requirements;
+ * all of these are in units of bytes.  These definitions are
+ * subject to some constraints:
+ * - IPA_HW_TBL_WIDTH must be non-zero
+ * - IPA_HW_TBL_SYSADDR_ALIGN must be a non-zero power of 2
+ * - IPA_HW_TBL_HDR_WIDTH must be non-zero
+ *
+ * Values could differ for different versions of IPA hardware.
+ * These values are for v3.5.1, found in the SDM845.
+ */
+#define IPA_HW_TBL_WIDTH		8
+#define IPA_HW_TBL_SYSADDR_ALIGN	128
+#define IPA_HW_TBL_HDR_WIDTH		8
+
+/**
+ * ipahal_dma_shared_mem_write_pyld() - Write to shared memory command payload
+ *
+ * Return a pointer to the payload for a DMA shared memory write immediate
+ * command, or null if one can't be allocated.  Result is dynamically
+ * allocated, and caller must ensure it gets released by providing it to
+ * ipahal_destroy_imm_cmd() when it is no longer needed.
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset);
+
+/**
+ * ipahal_hdr_init_local_pyld() - Header initialization command payload
+ * mem:		DMA buffer containing data for initialization
+ * offset:	Where in location IPA local memory to write
+ *
+ * Return a pointer to the payload for a header init local immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_hdr_init_local_pyld(struct ipa_dma_mem *mem, u32 offset);
+
+/**
+ * ipahal_ip_v4_routing_init_pyld() - IPv4 routing table initialization payload
+ * mem:		The IPv4 routing table data to be written
+ * hash_offset:	The location in IPA memory for a hashed routing table
+ * nhash_offset: The location in IPA memory for a non-hashed routing table
+ *
+ * Return a pointer to the payload for an IPv4 routing init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v4_routing_init_pyld(struct ipa_dma_mem *mem,
+				     u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_ip_v6_routing_init_pyld() - IPv6 routing table initialization payload
+ * mem:		The IPv6 routing table data to be written
+ * hash_offset:	The location in IPA memory for a hashed routing table
+ * nhash_offset: The location in IPA memory for a non-hashed routing table
+ *
+ * Return a pointer to the payload for an IPv4 routing init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v6_routing_init_pyld(struct ipa_dma_mem *mem,
+				     u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_ip_v4_filter_init_pyld() - IPv4 filter table initialization payload
+ * mem:		The IPv4 filter table data to be written
+ * hash_offset:	The location in IPA memory for a hashed filter table
+ * nhash_offset: The location in IPA memory for a non-hashed filter table
+ *
+ * Return a pointer to the payload for an IPv4 filter init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v4_filter_init_pyld(struct ipa_dma_mem *mem,
+				    u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_ip_v6_filter_init_pyld() - IPv6 filter table initialization payload
+ * mem:		The IPv6 filter table data to be written
+ * hash_offset:	The location in IPA memory for a hashed filter table
+ * nhash_offset: The location in IPA memory for a non-hashed filter table
+ *
+ * Return a pointer to the payload for an IPv4 filter init immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ *
+ * Return:	 Pointer to the immediate command payload, or NULL
+ */
+void *ipahal_ip_v6_filter_init_pyld(struct ipa_dma_mem *mem,
+				    u32 hash_offset, u32 nhash_offset);
+
+/**
+ * ipahal_dma_task_32b_addr_pyld() - 32-bit DMA task command payload
+ * mem:		DMA memory involved in the task
+ *
+ * Return a pointer to the payload for DMA task 32-bit address immediate
+ * command, or null if one can't be allocated.  Caller must ensure result
+ * gets released by providing it to ipahal_destroy_imm_cmd().
+ */
+void *ipahal_dma_task_32b_addr_pyld(struct ipa_dma_mem *mem);
+
+/**
+ * ipahal_payload_free() - Release an allocated immediate command payload
+ * @payload:	Payload to be released
+ */
+void ipahal_payload_free(void *payload);
+
+/**
+ * enum ipahal_pkt_status_opcode - Packet Status Opcode
+ * @IPAHAL_STATUS_OPCODE_PACKET_2ND_PASS: Packet Status generated as part of
+ *  IPA second processing pass for a packet (i.e. IPA XLAT processing for
+ *  the translated packet).
+ *
+ *  The values assigned here are assumed by ipa_pkt_status_parse()
+ *  to match values returned in the status_opcode field of a
+ *  ipa_pkt_status_hw structure inserted by the IPA in received
+ *  buffer.
+ */
+enum ipahal_pkt_status_opcode {
+	IPAHAL_PKT_STATUS_OPCODE_PACKET			= 0x01,
+	IPAHAL_PKT_STATUS_OPCODE_NEW_FRAG_RULE		= 0x02,
+	IPAHAL_PKT_STATUS_OPCODE_DROPPED_PACKET		= 0x04,
+	IPAHAL_PKT_STATUS_OPCODE_SUSPENDED_PACKET	= 0x08,
+	IPAHAL_PKT_STATUS_OPCODE_LOG			= 0x10,
+	IPAHAL_PKT_STATUS_OPCODE_DCMP			= 0x20,
+	IPAHAL_PKT_STATUS_OPCODE_PACKET_2ND_PASS	= 0x40,
+};
+
+/**
+ * enum ipahal_pkt_status_exception - Packet Status exception type
+ * @IPAHAL_PKT_STATUS_EXCEPTION_PACKET_LENGTH: formerly IHL exception.
+ *
+ * Note: IPTYPE, PACKET_LENGTH and PACKET_THRESHOLD exceptions means that
+ *  partial / no IP processing took place and corresponding Status Mask
+ *  fields should be ignored. Flt and rt info is not valid.
+ *
+ * NOTE:: Any change to this enum, need to change to
+ *	ipahal_pkt_status_exception_to_str array as well.
+ */
+enum ipahal_pkt_status_exception {
+	IPAHAL_PKT_STATUS_EXCEPTION_NONE = 0,
+	IPAHAL_PKT_STATUS_EXCEPTION_DEAGGR,
+	IPAHAL_PKT_STATUS_EXCEPTION_IPTYPE,
+	IPAHAL_PKT_STATUS_EXCEPTION_PACKET_LENGTH,
+	IPAHAL_PKT_STATUS_EXCEPTION_PACKET_THRESHOLD,
+	IPAHAL_PKT_STATUS_EXCEPTION_FRAG_RULE_MISS,
+	IPAHAL_PKT_STATUS_EXCEPTION_SW_FILT,
+	/* NAT and IPv6CT have the same value at HW.
+	 * NAT for IPv4 and IPv6CT for IPv6 exceptions
+	 */
+	IPAHAL_PKT_STATUS_EXCEPTION_NAT,
+	IPAHAL_PKT_STATUS_EXCEPTION_IPV6CT,
+	IPAHAL_PKT_STATUS_EXCEPTION_MAX,
+};
+
+/**
+ * enum ipahal_pkt_status_mask - Packet Status bitmask values of
+ *  the contained flags. This bitmask indicates flags on the properties of
+ *  the packet as well as IPA processing it may had.
+ * @TAG_VALID: Flag specifying if TAG and TAG info valid?
+ * @CKSUM_PROCESS: CSUM block processing flag: Was pkt processed by csum block?
+ *  If so, csum trailer exists
+ */
+enum ipahal_pkt_status_mask {
+	/* Other values are defined but are not specifically handled yet. */
+	IPAHAL_PKT_STATUS_MASK_CKSUM_PROCESS	= 0x0100,
+};
+
+/**
+ * struct ipahal_pkt_status - IPA status packet abstracted payload.
+ * @status_opcode: The type of status (Opcode).
+ * @exception: The first exception that took place.
+ *  In case of exception, endp_src_idx and pkt_len are always valid.
+ * @status_mask: Bit mask for flags on several properties on the packet
+ *  and processing it may passed at IPA.
+ * @pkt_len: Pkt pyld len including hdr and retained hdr if used. Does
+ *  not include padding or checksum trailer len.
+ * @endp_src_idx: Source end point index.
+ * @endp_dest_idx: Destination end point index.
+ *  Not valid in case of exception
+ * @metadata: meta data value used by packet
+ * @rt_miss: Routing miss flag: Was their a routing rule miss?
+ *
+ * This structure describes the status packet fields for the following
+ * status values: IPA_STATUS_PACKET, IPA_STATUS_DROPPED_PACKET,
+ * IPA_STATUS_SUSPENDED_PACKET.  Other status types have different status
+ * packet structure.  Note that the hardware supplies additional status
+ * information that is currently unused.
+ */
+struct ipahal_pkt_status {
+	enum ipahal_pkt_status_opcode status_opcode;
+	enum ipahal_pkt_status_exception exception;
+	enum ipahal_pkt_status_mask status_mask;
+	u32 pkt_len;
+	u8 endp_src_idx;
+	u8 endp_dest_idx;
+	u32 metadata;
+	bool rt_miss;
+};
+
+/**
+ * ipahal_pkt_status_get_size() - Get size of a hardware packet status
+ */
+u32 ipahal_pkt_status_get_size(void);
+
+/* ipahal_pkt_status_parse() - Parse packet status payload
+ * @unparsed_status:	Packet status read from hardware
+ * @status:		Buffer to hold parsed status information
+ */
+void ipahal_pkt_status_parse(const void *unparsed_status,
+			     struct ipahal_pkt_status *status);
+
+int ipahal_init(void);
+void ipahal_exit(void);
+
+/* Does the given ID represent rule miss? */
+bool ipahal_is_rule_miss_id(u32 id);
+
+int ipahal_rt_generate_empty_img(u32 route_count, struct ipa_dma_mem *mem);
+int ipahal_flt_generate_empty_img(u64 ep_bitmap, struct ipa_dma_mem *mem);
+
+/**
+ * ipahal_free_empty_img() - Free empty filter or route image
+ * @mem:	DMA memory containing filter/route data
+ */
+void ipahal_free_empty_img(struct ipa_dma_mem *mem);
+
+#endif /* _IPAHAL_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 05/12] soc: qcom: ipa: IPA interrupts and the microcontroller
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

The IPA has an interrupt line distinct from the interrupt used by
the GSI code.  Whereas GSI interrupts are generally related to
channel events (like transfer completions), IPA interrupts are
related to other events related to the IPA.  When the IPA IRQ fires,
an IPA interrupt status register indicates which IPA interrupt
events are being signaled.  IPA interrupts can be masked
independently, and can also be endependently enabled or disabled.

The IPA has an embedded microcontroller that can be used for
additional processing of messages passing through the IPA.  This
feature is generally not used by the current code.

Currently only three IPA interrupts are used:  one to trigger a
resume when in a suspended state; and two that allow the embedded
microcontroller to signal events.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_interrupts.c | 307 ++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_uc.c         | 336 +++++++++++++++++++++++++++++++
 2 files changed, 643 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_interrupts.c
 create mode 100644 drivers/net/ipa/ipa_uc.c

diff --git a/drivers/net/ipa/ipa_interrupts.c b/drivers/net/ipa/ipa_interrupts.c
new file mode 100644
index 000000000000..75cd81a1eab0
--- /dev/null
+++ b/drivers/net/ipa/ipa_interrupts.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+/*
+ * DOC: IPA Interrupts
+ *
+ * The IPA has an interrupt line distinct from the interrupt used
+ * by the GSI code.  Whereas GSI interrupts are generally related
+ * to channel events (like transfer completions), IPA interrupts are
+ * related to other events related to the IPA.  Some of the IPA
+ * interrupts come from a microcontroller embedded in the IPA.
+ * Each IPA interrupt type can be both masked and acknowledged
+ * independent of the others,
+ *
+ * So two of the IPA interrupts are initiated by the microcontroller.
+ * A third can be generated to signal the need for a wakeup/resume
+ * when the IPA has been suspended.  The modem can cause this event
+ * to occur (for example, for an incoming call).  There are other IPA
+ * events defined, but at this time only these three are supported.
+ */
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+#include "ipa_i.h"
+#include "ipa_reg.h"
+
+struct ipa_interrupt_info {
+	ipa_irq_handler_t handler;
+	enum ipa_irq_type interrupt;
+};
+
+#define IPA_IRQ_NUM_MAX	32	/* Number of IRQ bits in IPA interrupt mask */
+static struct ipa_interrupt_info ipa_interrupt_info[IPA_IRQ_NUM_MAX];
+
+static struct workqueue_struct *ipa_interrupt_wq;
+
+static void enable_tx_suspend_work_func(struct work_struct *work);
+static DECLARE_DELAYED_WORK(tx_suspend_work, enable_tx_suspend_work_func);
+
+static const int ipa_irq_mapping[] = {
+	[IPA_INVALID_IRQ]		= -1,
+	[IPA_UC_IRQ_0]			= 2,
+	[IPA_UC_IRQ_1]			= 3,
+	[IPA_TX_SUSPEND_IRQ]		= 14,
+};
+
+/* IPA interrupt handlers are called in contexts that can block */
+static void ipa_interrupt_work_func(struct work_struct *work);
+static DECLARE_WORK(ipa_interrupt_work, ipa_interrupt_work_func);
+
+/* Workaround disables TX_SUSPEND interrupt for this long */
+#define DISABLE_TX_SUSPEND_INTR_DELAY	msecs_to_jiffies(5)
+
+/* Disable the IPA TX_SUSPEND interrupt, and arrange for it to be
+ * re-enabled again in 5 milliseconds.
+ *
+ * This is part of a hardware bug workaround.
+ */
+static void ipa_tx_suspend_interrupt_wa(void)
+{
+	u32 val;
+
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val &= ~BIT(ipa_irq_mapping[IPA_TX_SUSPEND_IRQ]);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+
+	queue_delayed_work(ipa_interrupt_wq, &tx_suspend_work,
+			   DISABLE_TX_SUSPEND_INTR_DELAY);
+}
+
+static void ipa_handle_interrupt(int irq_num)
+{
+	struct ipa_interrupt_info *intr_info = &ipa_interrupt_info[irq_num];
+	u32 endpoints = 0;	/* Only TX_SUSPEND uses its interrupt_data */
+
+	if (!intr_info->handler)
+		return;
+
+	if (intr_info->interrupt == IPA_TX_SUSPEND_IRQ) {
+		/* Disable the suspend interrupt temporarily */
+		ipa_tx_suspend_interrupt_wa();
+
+		/* Get and clear mask of endpoints signaling TX_SUSPEND */
+		endpoints = ipa_read_reg_n(IPA_IRQ_SUSPEND_INFO_EE_N,
+					   IPA_EE_AP);
+		ipa_write_reg_n(IPA_SUSPEND_IRQ_CLR_EE_N, IPA_EE_AP, endpoints);
+	}
+
+	intr_info->handler(intr_info->interrupt, endpoints);
+}
+
+static inline bool is_uc_irq(int irq_num)
+{
+	enum ipa_irq_type interrupt = ipa_interrupt_info[irq_num].interrupt;
+
+	return interrupt != IPA_UC_IRQ_0 && interrupt != IPA_UC_IRQ_1;
+}
+
+static void ipa_process_interrupts(void)
+{
+	while (true) {
+		u32 ipa_intr_mask;
+		u32 imask;	/* one set bit */
+
+		/* Determine which interrupts have fired, then examine only
+		 * those that are enabled.  Note that a suspend interrupt
+		 * bug forces us to re-read the enabled mask every time to
+		 * avoid an endless loop.
+		 */
+		ipa_intr_mask = ipa_read_reg_n(IPA_IRQ_STTS_EE_N, IPA_EE_AP);
+		ipa_intr_mask &= ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+
+		if (!ipa_intr_mask)
+			break;
+
+		do {
+			int i = __ffs(ipa_intr_mask);
+			bool uc_irq = is_uc_irq(i);
+
+			imask = BIT(i);
+
+			/* Clear uC interrupt before processing to avoid
+			 * clearing unhandled interrupts
+			 */
+			if (uc_irq)
+				ipa_write_reg_n(IPA_IRQ_CLR_EE_N, IPA_EE_AP,
+						imask);
+
+			ipa_handle_interrupt(i);
+
+			/* Clear non-uC interrupt after processing
+			 * to avoid clearing interrupt data
+			 */
+			if (!uc_irq)
+				ipa_write_reg_n(IPA_IRQ_CLR_EE_N, IPA_EE_AP,
+						imask);
+		} while ((ipa_intr_mask ^= imask));
+	}
+}
+
+static void ipa_interrupt_work_func(struct work_struct *work)
+{
+	ipa_client_add();
+
+	ipa_process_interrupts();
+
+	ipa_client_remove();
+}
+
+static irqreturn_t ipa_isr(int irq, void *ctxt)
+{
+	/* Schedule handling (if not already scheduled) */
+	queue_work(ipa_interrupt_wq, &ipa_interrupt_work);
+
+	return IRQ_HANDLED;
+}
+
+/* Re-enable the IPA TX_SUSPEND interrupt after having been disabled
+ * for a moment by ipa_tx_suspend_interrupt_wa().  This is part of a
+ * workaround for a hardware bug.
+ */
+static void enable_tx_suspend_work_func(struct work_struct *work)
+{
+	u32 val;
+
+	ipa_client_add();
+
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val |= BIT(ipa_irq_mapping[IPA_TX_SUSPEND_IRQ]);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+
+	ipa_process_interrupts();
+
+	ipa_client_remove();
+}
+
+/* Register SUSPEND_IRQ_EN_EE_N_ADDR for L2 interrupt. */
+static void tx_suspend_enable(void)
+{
+	enum ipa_client_type client;
+	u32 val = ~0;
+
+	/* Compute the mask to use (bits set for all non-modem endpoints) */
+	for (client = 0; client < IPA_CLIENT_MAX; client++)
+		if (ipa_modem_consumer(client) || ipa_modem_producer(client))
+			val &= ~BIT(ipa_client_ep_id(client));
+
+	ipa_write_reg_n(IPA_SUSPEND_IRQ_EN_EE_N, IPA_EE_AP, val);
+}
+
+/* Unregister SUSPEND_IRQ_EN_EE_N_ADDR for L2 interrupt. */
+static void tx_suspend_disable(void)
+{
+	ipa_write_reg_n(IPA_SUSPEND_IRQ_EN_EE_N, IPA_EE_AP, 0);
+}
+
+/**
+ * ipa_add_interrupt_handler() - Adds handler for an IPA interrupt
+ * @interrupt:		IPA interrupt type
+ * @handler:		The handler for that interrupt
+ *
+ * Adds handler to an IPA interrupt type and enable it.  IPA interrupt
+ * handlers are allowed to block (they aren't run in interrupt context).
+ */
+void ipa_add_interrupt_handler(enum ipa_irq_type interrupt,
+			       ipa_irq_handler_t handler)
+{
+	int irq_num = ipa_irq_mapping[interrupt];
+	struct ipa_interrupt_info *intr_info;
+	u32 val;
+
+	intr_info = &ipa_interrupt_info[irq_num];
+	intr_info->handler = handler;
+	intr_info->interrupt = interrupt;
+
+	/* Enable the IPA interrupt */
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val |= BIT(irq_num);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+
+	if (interrupt == IPA_TX_SUSPEND_IRQ)
+		tx_suspend_enable();
+}
+
+/**
+ * ipa_remove_interrupt_handler() - Removes handler for an IPA interrupt type
+ * @interrupt:		IPA interrupt type
+ *
+ * Remove an IPA interrupt handler and disable it.
+ */
+void ipa_remove_interrupt_handler(enum ipa_irq_type interrupt)
+{
+	int irq_num = ipa_irq_mapping[interrupt];
+	struct ipa_interrupt_info *intr_info;
+	u32 val;
+
+	intr_info = &ipa_interrupt_info[irq_num];
+	intr_info->handler = NULL;
+	intr_info->interrupt = IPA_INVALID_IRQ;
+
+	if (interrupt == IPA_TX_SUSPEND_IRQ)
+		tx_suspend_disable();
+
+	/* Disable the interrupt */
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val &= ~BIT(irq_num);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+}
+
+/**
+ * ipa_interrupts_init() - Initialize the IPA interrupts framework
+ */
+int ipa_interrupts_init(void)
+{
+	int ret;
+
+	ret = request_irq(ipa_ctx->ipa_irq, ipa_isr, IRQF_TRIGGER_RISING,
+			  "ipa", ipa_ctx->dev);
+	if (ret)
+		return ret;
+
+	ipa_interrupt_wq = alloc_ordered_workqueue("ipa_interrupt_wq", 0);
+	if (ipa_interrupt_wq)
+		return 0;
+
+	free_irq(ipa_ctx->ipa_irq, ipa_ctx->dev);
+
+	return -ENOMEM;
+}
+
+/**
+ * ipa_suspend_active_aggr_wa() - Emulate suspend interrupt
+ * @ep_id:	Endpoint on which to emulate a suspend
+ *
+ *  Emulate suspend IRQ to unsuspend a client suspended with an open
+ *  aggregation frame.  This is to work around a hardware issue
+ *  where an IRQ is not generated as it should be when this occurs.
+ */
+void ipa_suspend_active_aggr_wa(u32 ep_id)
+{
+	struct ipa_reg_aggr_force_close force_close;
+	struct ipa_interrupt_info *intr_info;
+	u32 clnt_mask;
+	int irq_num;
+
+	irq_num = ipa_irq_mapping[IPA_TX_SUSPEND_IRQ];
+	intr_info = &ipa_interrupt_info[irq_num];
+	clnt_mask = BIT(ep_id);
+
+	/* Nothing to do if the endpoint doesn't have aggregation open */
+	if (!(ipa_read_reg(IPA_STATE_AGGR_ACTIVE) & clnt_mask))
+		return;
+
+	/* Force close aggregation */
+	ipa_reg_aggr_force_close(&force_close, clnt_mask);
+	ipa_write_reg_fields(IPA_AGGR_FORCE_CLOSE, &force_close);
+
+	/* Simulate suspend IRQ */
+	ipa_assert(!in_interrupt());
+	if (intr_info->handler)
+		intr_info->handler(intr_info->interrupt, clnt_mask);
+}
diff --git a/drivers/net/ipa/ipa_uc.c b/drivers/net/ipa/ipa_uc.c
new file mode 100644
index 000000000000..2065e53f3601
--- /dev/null
+++ b/drivers/net/ipa/ipa_uc.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/delay.h>
+
+#include "ipa_i.h"
+
+/**
+ * DOC:  The IPA Embedded Microcontroller
+ *
+ * The IPA incorporates an embedded microcontroller that is able to
+ * do some additional handling/offloading of network activity.  The
+ * current code makes essentially no use of the microcontroller.
+ * Despite not being used, the microcontroller still requires some
+ * initialization, and it needs to be notified in the event the AP
+ * crashes.  The IPA embedded microcontroller represents another IPA
+ * execution environment (in addition to the AP subsystem and
+ * modem).
+ */
+
+/* Supports hardware interface version 0x2000 */
+
+#define IPA_RAM_UC_SMEM_SIZE	128	/* Size of shared memory area */
+
+/* Delay to allow a the microcontroller to save state when crashing */
+#define IPA_SEND_DELAY		100	/* microseconds */
+
+/*
+ * The IPA has an embedded microcontroller that is capable of doing
+ * more general-purpose processing, for example for handling certain
+ * exceptional conditions.  When it has completed its boot sequence
+ * it signals the AP with an interrupt.  At this time we don't use
+ * any of the microcontroller capabilities, but we do handle the
+ * "ready" interrupt.  We also notify it (by sending it a special
+ * command) in the event of a crash.
+ *
+ * A 128 byte block of structured memory within the IPA SRAM is used
+ * to communicate between the AP and the microcontroller embedded in
+ * the IPA.
+ *
+ * To send a command to the microcontroller, the AP fills in the
+ * command opcode and command parameter fields in this area, then
+ * writes a register to signal to the microcontroller the command is
+ * available.  When the microcontroller has executed the command, it
+ * writes response data to this shared area, then issues a response
+ * interrupt (micrcontroller IRQ 1) to the AP.  The response
+ * includes a "response operation" that indicates the completion,
+ * along with a "response parameter" which encodes the original
+ * command and the command's status (result).
+ *
+ * The shared area is also used to communicate events asynchronously
+ * from the microcontroller to the AP.  Events are signaled using
+ * the event interrupt (micrcontroller IRQ 0).  The microcontroller
+ * fills in an "event operation" and "event parameter" before
+ * triggering the interrupt.
+ *
+ * Some additional information is also found in this shared area,
+ * but is currently unused by the IPA driver.
+ *
+ * All other space in the shared area is reserved, and must not be
+ * read or written by the AP.
+ */
+
+/** struct ipa_uc_shared_area - AP/microcontroller shared memory area
+ *
+ * @command: command code (AP->microcontroller)
+ * @command_param: low 32 bits of command parameter (AP->microcontroller)
+ * @command_param_hi: high 32 bits of command parameter (AP->microcontroller)
+ *
+ * @response: response code (microcontroller->AP)
+ * @response_param: response parameter (microcontroller->AP)
+ *
+ * @event: event code (microcontroller->AP)
+ * @event_param: event parameter (microcontroller->AP)
+ *
+ * @first_error_address: address of first error-source on SNOC
+ * @hw_state: state of hardware (including error type information)
+ * @warning_counter: counter of non-fatal hardware errors
+ * @interface_version: hardware-reported interface version
+ */
+struct ipa_uc_shared_area {
+	u32 command		: 8;	/* enum ipa_uc_command */
+	/* 3 reserved bytes */
+	u32 command_param;
+	u32 command_param_hi;
+
+	u32 response		: 8; 	/* enum ipa_uc_response */
+	/* 3 reserved bytes */
+	u32 response_param;
+
+	u32 event		: 8;	/* enum ipa_uc_event */
+	/* 3 reserved bytes */
+	u32 event_param;
+
+	u32 first_error_address;
+	u32 hw_state		: 8,
+	    warning_counter	: 8,
+	    reserved		: 16;
+	u32 interface_version	: 16;
+	/* 2 reserved bytes */
+};
+
+/** struct ipa_uc_ctx - IPA microcontroller context
+ *
+ * @uc_loaded: whether microcontroller has been loaded
+ * @shared: pointer to AP/microcontroller shared memory area
+ */
+struct ipa_uc_ctx {
+	bool uc_loaded;
+	struct ipa_uc_shared_area *shared;
+} ipa_uc_ctx;
+
+/*
+ * Microcontroller event codes, error codes, commands, and responses
+ * to commands all encode both a "code" and a "feature" in their
+ * 8-bit numeric value.  The top 3 bits represent the feature, and
+ * the bottom 5 bits represent the code.  A "common" feature uses
+ * feature code 0, and at this time we only deal with common
+ * features.  Because of this we can just ignore the feature bits
+ * and define the values of symbols in  the following enumerated
+ * types by just their code values.
+ */
+
+/** enum ipa_uc_event - common cpu events (microcontroller->AP)
+ *
+ * @IPA_UC_EVENT_NO_OP: no event present
+ * @IPA_UC_EVENT_ERROR: system error has been detected
+ * @IPA_UC_EVENT_LOG_INFO: logging information available
+ */
+enum ipa_uc_event {
+	IPA_UC_EVENT_NO_OP     = 0,
+	IPA_UC_EVENT_ERROR     = 1,
+	IPA_UC_EVENT_LOG_INFO  = 2,
+};
+
+/** enum ipa_uc_error - common error types (microcontroller->AP)
+ *
+ * @IPA_UC_ERROR_NONE: no error
+ * @IPA_UC_ERROR_INVALID_DOORBELL: invalid data read from doorbell
+ * @IPA_UC_ERROR_DMA: unexpected DMA error
+ * @IPA_UC_ERROR_FATAL_SYSTEM: microcontroller has crashed and requires reset
+ * @IPA_UC_ERROR_INVALID_OPCODE: invalid opcode sent
+ * @IPA_UC_ERROR_INVALID_PARAMS: invalid params for the requested command
+ * @IPA_UC_ERROR_CONS_DISABLE_CMD_GSI_STOP: consumer endpoint stop failure
+ * @IPA_UC_ERROR_PROD_DISABLE_CMD_GSI_STOP: producer endpoint stop failure
+ * @IPA_UC_ERROR_CH_NOT_EMPTY: micrcontroller GSI channel is not empty
+ */
+enum ipa_uc_error {
+	IPA_UC_ERROR_NONE			= 0,
+	IPA_UC_ERROR_INVALID_DOORBELL		= 1,
+	IPA_UC_ERROR_DMA			= 2,
+	IPA_UC_ERROR_FATAL_SYSTEM		= 3,
+	IPA_UC_ERROR_INVALID_OPCODE		= 4,
+	IPA_UC_ERROR_INVALID_PARAMS		= 5,
+	IPA_UC_ERROR_CONS_DISABLE_CMD_GSI_STOP	= 6,
+	IPA_UC_ERROR_PROD_DISABLE_CMD_GSI_STOP	= 7,
+	IPA_UC_ERROR_CH_NOT_EMPTY		= 8,
+};
+
+/** enum ipa_uc_command - commands from the AP to the microcontroller
+ *
+ * @IPA_UC_COMMAND_NO_OP: no operation
+ * @IPA_UC_COMMAND_UPDATE_FLAGS: request to re-read configuration flags
+ * @IPA_UC_COMMAND_DEBUG_RUN_TEST: request to run hardware test
+ * @IPA_UC_COMMAND_DEBUG_GET_INFO: request to read internal debug information
+ * @IPA_UC_COMMAND_ERR_FATAL: AP system crash notification
+ * @IPA_UC_COMMAND_CLK_GATE: request hardware to enter clock gated state
+ * @IPA_UC_COMMAND_CLK_UNGATE: request hardware to enter clock ungated state
+ * @IPA_UC_COMMAND_MEMCPY: request hardware to perform memcpy
+ * @IPA_UC_COMMAND_RESET_PIPE: request endpoint reset
+ * @IPA_UC_COMMAND_REG_WRITE: request a register be written
+ * @IPA_UC_COMMAND_GSI_CH_EMPTY: request to determine whether channel is empty
+ */
+enum ipa_uc_command {
+	IPA_UC_COMMAND_NO_OP		= 0,
+	IPA_UC_COMMAND_UPDATE_FLAGS	= 1,
+	IPA_UC_COMMAND_DEBUG_RUN_TEST	= 2,
+	IPA_UC_COMMAND_DEBUG_GET_INFO	= 3,
+	IPA_UC_COMMAND_ERR_FATAL	= 4,
+	IPA_UC_COMMAND_CLK_GATE		= 5,
+	IPA_UC_COMMAND_CLK_UNGATE	= 6,
+	IPA_UC_COMMAND_MEMCPY		= 7,
+	IPA_UC_COMMAND_RESET_PIPE	= 8,
+	IPA_UC_COMMAND_REG_WRITE	= 9,
+	IPA_UC_COMMAND_GSI_CH_EMPTY	= 10,
+};
+
+/** enum ipa_uc_response - common hardware response codes
+ *
+ * @IPA_UC_RESPONSE_NO_OP: no operation
+ * @IPA_UC_RESPONSE_INIT_COMPLETED: microcontroller ready
+ * @IPA_UC_RESPONSE_CMD_COMPLETED: AP-issued command has completed
+ * @IPA_UC_RESPONSE_DEBUG_GET_INFO: get debug info
+ */
+enum ipa_uc_response {
+	IPA_UC_RESPONSE_NO_OP		= 0,
+	IPA_UC_RESPONSE_INIT_COMPLETED	= 1,
+	IPA_UC_RESPONSE_CMD_COMPLETED	= 2,
+	IPA_UC_RESPONSE_DEBUG_GET_INFO	= 3,
+};
+
+/** union ipa_uc_event_data - microcontroller->AP event data
+ *
+ * @error_type: ipa_uc_error error type value
+ * @raw32b: 32-bit register value (used when reading)
+ */
+union ipa_uc_event_data {
+	u8 error_type;	/* enum ipa_uc_error */
+	u32 raw32b;
+} __packed;
+
+/** union ipa_uc_response_data - response to AP command
+ *
+ * @command: the AP issued command this is responding to
+ * @status: 0 for success indication, otherwise failure
+ * @raw32b: 32-bit register value (used when reading)
+ */
+union ipa_uc_response_data {
+	struct ipa_uc_response_param {
+		u8 command;	/* enum ipa_uc_command */
+		u8 status;	/* enum ipa_uc_error */
+	} params;
+	u32 raw32b;
+} __packed;
+
+/** ipa_uc_loaded() - tell whether the microcontroller has been loaded
+ *
+ * Returns true if the microcontroller is loaded, false otherwise
+ */
+bool ipa_uc_loaded(void)
+{
+	return ipa_uc_ctx.uc_loaded;
+}
+
+static void
+ipa_uc_event_handler(enum ipa_irq_type interrupt, u32 interrupt_data)
+{
+	struct ipa_uc_shared_area *shared = ipa_uc_ctx.shared;
+	union ipa_uc_event_data event_param;
+	u8 event;
+
+	event = shared->event;
+	event_param.raw32b = shared->event_param;
+
+	/* General handling */
+	if (event == IPA_UC_EVENT_ERROR) {
+		ipa_err("uC error type 0x%02x timestamp 0x%08x\n",
+			event_param.error_type, ipa_read_reg(IPA_TAG_TIMER));
+		ipa_bug();
+	} else {
+		ipa_err("unsupported uC event opcode=%u\n", event);
+	}
+}
+
+static void
+ipa_uc_response_hdlr(enum ipa_irq_type interrupt, u32 interrupt_data)
+{
+	struct ipa_uc_shared_area *shared = ipa_uc_ctx.shared;
+	union ipa_uc_response_data response_data;
+	u8 response;
+
+	response = shared->response;
+
+	/* An INIT_COMPLETED response message is sent to the AP by
+	 * the microcontroller when it is operational.  Other than
+	 * this, the AP should only receive responses from the
+	 * microntroller when it has sent it a request message.
+	 */
+	if (response == IPA_UC_RESPONSE_INIT_COMPLETED) {
+		/* The proxy vote is held until uC is loaded to ensure that
+		 * IPA_HW_2_CPU_RESPONSE_INIT_COMPLETED is received.
+		 */
+		ipa_proxy_clk_unvote();
+		ipa_uc_ctx.uc_loaded = true;
+	} else if (response == IPA_UC_RESPONSE_CMD_COMPLETED) {
+		response_data.raw32b = shared->response_param;
+		ipa_err("uC command response code %u status %u\n",
+			response_data.params.command,
+			response_data.params.status);
+	} else {
+		ipa_err("Unsupported uC rsp opcode = %u\n", response);
+	}
+}
+
+/** ipa_uc_init() - Initialize the microcontroller
+ *
+ * Returns pointer to microcontroller context on success, NULL otherwise
+ */
+struct ipa_uc_ctx *ipa_uc_init(phys_addr_t phys_addr)
+{
+	phys_addr += ipa_reg_n_offset(IPA_SRAM_DIRECT_ACCESS_N, 0);
+	ipa_uc_ctx.shared = ioremap(phys_addr, IPA_RAM_UC_SMEM_SIZE);
+	if (!ipa_uc_ctx.shared)
+		return NULL;
+
+	ipa_add_interrupt_handler(IPA_UC_IRQ_0, ipa_uc_event_handler);
+	ipa_add_interrupt_handler(IPA_UC_IRQ_1, ipa_uc_response_hdlr);
+
+	return &ipa_uc_ctx;
+}
+
+/* Send a command to the microcontroller */
+static void send_uc_command(u32 command, u32 command_param)
+{
+	struct ipa_uc_shared_area *shared = ipa_uc_ctx.shared;
+
+	shared->command = command;
+	shared->command_param = command_param;
+	shared->command_param_hi = 0;
+	shared->response = 0;
+	shared->response_param = 0;
+
+	wmb();	/* ensure write to shared memory is done before triggering uc */
+
+	ipa_write_reg_n(IPA_IRQ_EE_UC_N, IPA_EE_AP, 0x1);
+}
+
+void ipa_uc_panic_notifier(void)
+{
+	if (!ipa_uc_ctx.uc_loaded)
+		return;
+
+	if (!ipa_client_add_additional())
+		return;
+
+	send_uc_command(IPA_UC_COMMAND_ERR_FATAL, 0);
+
+	/* give uc enough time to save state */
+	udelay(IPA_SEND_DELAY);
+
+	ipa_client_remove();
+}
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 05/12] soc: qcom: ipa: IPA interrupts and the microcontroller
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

The IPA has an interrupt line distinct from the interrupt used by
the GSI code.  Whereas GSI interrupts are generally related to
channel events (like transfer completions), IPA interrupts are
related to other events related to the IPA.  When the IPA IRQ fires,
an IPA interrupt status register indicates which IPA interrupt
events are being signaled.  IPA interrupts can be masked
independently, and can also be endependently enabled or disabled.

The IPA has an embedded microcontroller that can be used for
additional processing of messages passing through the IPA.  This
feature is generally not used by the current code.

Currently only three IPA interrupts are used:  one to trigger a
resume when in a suspended state; and two that allow the embedded
microcontroller to signal events.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_interrupts.c | 307 ++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_uc.c         | 336 +++++++++++++++++++++++++++++++
 2 files changed, 643 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_interrupts.c
 create mode 100644 drivers/net/ipa/ipa_uc.c

diff --git a/drivers/net/ipa/ipa_interrupts.c b/drivers/net/ipa/ipa_interrupts.c
new file mode 100644
index 000000000000..75cd81a1eab0
--- /dev/null
+++ b/drivers/net/ipa/ipa_interrupts.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+/*
+ * DOC: IPA Interrupts
+ *
+ * The IPA has an interrupt line distinct from the interrupt used
+ * by the GSI code.  Whereas GSI interrupts are generally related
+ * to channel events (like transfer completions), IPA interrupts are
+ * related to other events related to the IPA.  Some of the IPA
+ * interrupts come from a microcontroller embedded in the IPA.
+ * Each IPA interrupt type can be both masked and acknowledged
+ * independent of the others,
+ *
+ * So two of the IPA interrupts are initiated by the microcontroller.
+ * A third can be generated to signal the need for a wakeup/resume
+ * when the IPA has been suspended.  The modem can cause this event
+ * to occur (for example, for an incoming call).  There are other IPA
+ * events defined, but at this time only these three are supported.
+ */
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+#include "ipa_i.h"
+#include "ipa_reg.h"
+
+struct ipa_interrupt_info {
+	ipa_irq_handler_t handler;
+	enum ipa_irq_type interrupt;
+};
+
+#define IPA_IRQ_NUM_MAX	32	/* Number of IRQ bits in IPA interrupt mask */
+static struct ipa_interrupt_info ipa_interrupt_info[IPA_IRQ_NUM_MAX];
+
+static struct workqueue_struct *ipa_interrupt_wq;
+
+static void enable_tx_suspend_work_func(struct work_struct *work);
+static DECLARE_DELAYED_WORK(tx_suspend_work, enable_tx_suspend_work_func);
+
+static const int ipa_irq_mapping[] = {
+	[IPA_INVALID_IRQ]		= -1,
+	[IPA_UC_IRQ_0]			= 2,
+	[IPA_UC_IRQ_1]			= 3,
+	[IPA_TX_SUSPEND_IRQ]		= 14,
+};
+
+/* IPA interrupt handlers are called in contexts that can block */
+static void ipa_interrupt_work_func(struct work_struct *work);
+static DECLARE_WORK(ipa_interrupt_work, ipa_interrupt_work_func);
+
+/* Workaround disables TX_SUSPEND interrupt for this long */
+#define DISABLE_TX_SUSPEND_INTR_DELAY	msecs_to_jiffies(5)
+
+/* Disable the IPA TX_SUSPEND interrupt, and arrange for it to be
+ * re-enabled again in 5 milliseconds.
+ *
+ * This is part of a hardware bug workaround.
+ */
+static void ipa_tx_suspend_interrupt_wa(void)
+{
+	u32 val;
+
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val &= ~BIT(ipa_irq_mapping[IPA_TX_SUSPEND_IRQ]);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+
+	queue_delayed_work(ipa_interrupt_wq, &tx_suspend_work,
+			   DISABLE_TX_SUSPEND_INTR_DELAY);
+}
+
+static void ipa_handle_interrupt(int irq_num)
+{
+	struct ipa_interrupt_info *intr_info = &ipa_interrupt_info[irq_num];
+	u32 endpoints = 0;	/* Only TX_SUSPEND uses its interrupt_data */
+
+	if (!intr_info->handler)
+		return;
+
+	if (intr_info->interrupt == IPA_TX_SUSPEND_IRQ) {
+		/* Disable the suspend interrupt temporarily */
+		ipa_tx_suspend_interrupt_wa();
+
+		/* Get and clear mask of endpoints signaling TX_SUSPEND */
+		endpoints = ipa_read_reg_n(IPA_IRQ_SUSPEND_INFO_EE_N,
+					   IPA_EE_AP);
+		ipa_write_reg_n(IPA_SUSPEND_IRQ_CLR_EE_N, IPA_EE_AP, endpoints);
+	}
+
+	intr_info->handler(intr_info->interrupt, endpoints);
+}
+
+static inline bool is_uc_irq(int irq_num)
+{
+	enum ipa_irq_type interrupt = ipa_interrupt_info[irq_num].interrupt;
+
+	return interrupt != IPA_UC_IRQ_0 && interrupt != IPA_UC_IRQ_1;
+}
+
+static void ipa_process_interrupts(void)
+{
+	while (true) {
+		u32 ipa_intr_mask;
+		u32 imask;	/* one set bit */
+
+		/* Determine which interrupts have fired, then examine only
+		 * those that are enabled.  Note that a suspend interrupt
+		 * bug forces us to re-read the enabled mask every time to
+		 * avoid an endless loop.
+		 */
+		ipa_intr_mask = ipa_read_reg_n(IPA_IRQ_STTS_EE_N, IPA_EE_AP);
+		ipa_intr_mask &= ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+
+		if (!ipa_intr_mask)
+			break;
+
+		do {
+			int i = __ffs(ipa_intr_mask);
+			bool uc_irq = is_uc_irq(i);
+
+			imask = BIT(i);
+
+			/* Clear uC interrupt before processing to avoid
+			 * clearing unhandled interrupts
+			 */
+			if (uc_irq)
+				ipa_write_reg_n(IPA_IRQ_CLR_EE_N, IPA_EE_AP,
+						imask);
+
+			ipa_handle_interrupt(i);
+
+			/* Clear non-uC interrupt after processing
+			 * to avoid clearing interrupt data
+			 */
+			if (!uc_irq)
+				ipa_write_reg_n(IPA_IRQ_CLR_EE_N, IPA_EE_AP,
+						imask);
+		} while ((ipa_intr_mask ^= imask));
+	}
+}
+
+static void ipa_interrupt_work_func(struct work_struct *work)
+{
+	ipa_client_add();
+
+	ipa_process_interrupts();
+
+	ipa_client_remove();
+}
+
+static irqreturn_t ipa_isr(int irq, void *ctxt)
+{
+	/* Schedule handling (if not already scheduled) */
+	queue_work(ipa_interrupt_wq, &ipa_interrupt_work);
+
+	return IRQ_HANDLED;
+}
+
+/* Re-enable the IPA TX_SUSPEND interrupt after having been disabled
+ * for a moment by ipa_tx_suspend_interrupt_wa().  This is part of a
+ * workaround for a hardware bug.
+ */
+static void enable_tx_suspend_work_func(struct work_struct *work)
+{
+	u32 val;
+
+	ipa_client_add();
+
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val |= BIT(ipa_irq_mapping[IPA_TX_SUSPEND_IRQ]);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+
+	ipa_process_interrupts();
+
+	ipa_client_remove();
+}
+
+/* Register SUSPEND_IRQ_EN_EE_N_ADDR for L2 interrupt. */
+static void tx_suspend_enable(void)
+{
+	enum ipa_client_type client;
+	u32 val = ~0;
+
+	/* Compute the mask to use (bits set for all non-modem endpoints) */
+	for (client = 0; client < IPA_CLIENT_MAX; client++)
+		if (ipa_modem_consumer(client) || ipa_modem_producer(client))
+			val &= ~BIT(ipa_client_ep_id(client));
+
+	ipa_write_reg_n(IPA_SUSPEND_IRQ_EN_EE_N, IPA_EE_AP, val);
+}
+
+/* Unregister SUSPEND_IRQ_EN_EE_N_ADDR for L2 interrupt. */
+static void tx_suspend_disable(void)
+{
+	ipa_write_reg_n(IPA_SUSPEND_IRQ_EN_EE_N, IPA_EE_AP, 0);
+}
+
+/**
+ * ipa_add_interrupt_handler() - Adds handler for an IPA interrupt
+ * @interrupt:		IPA interrupt type
+ * @handler:		The handler for that interrupt
+ *
+ * Adds handler to an IPA interrupt type and enable it.  IPA interrupt
+ * handlers are allowed to block (they aren't run in interrupt context).
+ */
+void ipa_add_interrupt_handler(enum ipa_irq_type interrupt,
+			       ipa_irq_handler_t handler)
+{
+	int irq_num = ipa_irq_mapping[interrupt];
+	struct ipa_interrupt_info *intr_info;
+	u32 val;
+
+	intr_info = &ipa_interrupt_info[irq_num];
+	intr_info->handler = handler;
+	intr_info->interrupt = interrupt;
+
+	/* Enable the IPA interrupt */
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val |= BIT(irq_num);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+
+	if (interrupt == IPA_TX_SUSPEND_IRQ)
+		tx_suspend_enable();
+}
+
+/**
+ * ipa_remove_interrupt_handler() - Removes handler for an IPA interrupt type
+ * @interrupt:		IPA interrupt type
+ *
+ * Remove an IPA interrupt handler and disable it.
+ */
+void ipa_remove_interrupt_handler(enum ipa_irq_type interrupt)
+{
+	int irq_num = ipa_irq_mapping[interrupt];
+	struct ipa_interrupt_info *intr_info;
+	u32 val;
+
+	intr_info = &ipa_interrupt_info[irq_num];
+	intr_info->handler = NULL;
+	intr_info->interrupt = IPA_INVALID_IRQ;
+
+	if (interrupt == IPA_TX_SUSPEND_IRQ)
+		tx_suspend_disable();
+
+	/* Disable the interrupt */
+	val = ipa_read_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP);
+	val &= ~BIT(irq_num);
+	ipa_write_reg_n(IPA_IRQ_EN_EE_N, IPA_EE_AP, val);
+}
+
+/**
+ * ipa_interrupts_init() - Initialize the IPA interrupts framework
+ */
+int ipa_interrupts_init(void)
+{
+	int ret;
+
+	ret = request_irq(ipa_ctx->ipa_irq, ipa_isr, IRQF_TRIGGER_RISING,
+			  "ipa", ipa_ctx->dev);
+	if (ret)
+		return ret;
+
+	ipa_interrupt_wq = alloc_ordered_workqueue("ipa_interrupt_wq", 0);
+	if (ipa_interrupt_wq)
+		return 0;
+
+	free_irq(ipa_ctx->ipa_irq, ipa_ctx->dev);
+
+	return -ENOMEM;
+}
+
+/**
+ * ipa_suspend_active_aggr_wa() - Emulate suspend interrupt
+ * @ep_id:	Endpoint on which to emulate a suspend
+ *
+ *  Emulate suspend IRQ to unsuspend a client suspended with an open
+ *  aggregation frame.  This is to work around a hardware issue
+ *  where an IRQ is not generated as it should be when this occurs.
+ */
+void ipa_suspend_active_aggr_wa(u32 ep_id)
+{
+	struct ipa_reg_aggr_force_close force_close;
+	struct ipa_interrupt_info *intr_info;
+	u32 clnt_mask;
+	int irq_num;
+
+	irq_num = ipa_irq_mapping[IPA_TX_SUSPEND_IRQ];
+	intr_info = &ipa_interrupt_info[irq_num];
+	clnt_mask = BIT(ep_id);
+
+	/* Nothing to do if the endpoint doesn't have aggregation open */
+	if (!(ipa_read_reg(IPA_STATE_AGGR_ACTIVE) & clnt_mask))
+		return;
+
+	/* Force close aggregation */
+	ipa_reg_aggr_force_close(&force_close, clnt_mask);
+	ipa_write_reg_fields(IPA_AGGR_FORCE_CLOSE, &force_close);
+
+	/* Simulate suspend IRQ */
+	ipa_assert(!in_interrupt());
+	if (intr_info->handler)
+		intr_info->handler(intr_info->interrupt, clnt_mask);
+}
diff --git a/drivers/net/ipa/ipa_uc.c b/drivers/net/ipa/ipa_uc.c
new file mode 100644
index 000000000000..2065e53f3601
--- /dev/null
+++ b/drivers/net/ipa/ipa_uc.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/delay.h>
+
+#include "ipa_i.h"
+
+/**
+ * DOC:  The IPA Embedded Microcontroller
+ *
+ * The IPA incorporates an embedded microcontroller that is able to
+ * do some additional handling/offloading of network activity.  The
+ * current code makes essentially no use of the microcontroller.
+ * Despite not being used, the microcontroller still requires some
+ * initialization, and it needs to be notified in the event the AP
+ * crashes.  The IPA embedded microcontroller represents another IPA
+ * execution environment (in addition to the AP subsystem and
+ * modem).
+ */
+
+/* Supports hardware interface version 0x2000 */
+
+#define IPA_RAM_UC_SMEM_SIZE	128	/* Size of shared memory area */
+
+/* Delay to allow a the microcontroller to save state when crashing */
+#define IPA_SEND_DELAY		100	/* microseconds */
+
+/*
+ * The IPA has an embedded microcontroller that is capable of doing
+ * more general-purpose processing, for example for handling certain
+ * exceptional conditions.  When it has completed its boot sequence
+ * it signals the AP with an interrupt.  At this time we don't use
+ * any of the microcontroller capabilities, but we do handle the
+ * "ready" interrupt.  We also notify it (by sending it a special
+ * command) in the event of a crash.
+ *
+ * A 128 byte block of structured memory within the IPA SRAM is used
+ * to communicate between the AP and the microcontroller embedded in
+ * the IPA.
+ *
+ * To send a command to the microcontroller, the AP fills in the
+ * command opcode and command parameter fields in this area, then
+ * writes a register to signal to the microcontroller the command is
+ * available.  When the microcontroller has executed the command, it
+ * writes response data to this shared area, then issues a response
+ * interrupt (micrcontroller IRQ 1) to the AP.  The response
+ * includes a "response operation" that indicates the completion,
+ * along with a "response parameter" which encodes the original
+ * command and the command's status (result).
+ *
+ * The shared area is also used to communicate events asynchronously
+ * from the microcontroller to the AP.  Events are signaled using
+ * the event interrupt (micrcontroller IRQ 0).  The microcontroller
+ * fills in an "event operation" and "event parameter" before
+ * triggering the interrupt.
+ *
+ * Some additional information is also found in this shared area,
+ * but is currently unused by the IPA driver.
+ *
+ * All other space in the shared area is reserved, and must not be
+ * read or written by the AP.
+ */
+
+/** struct ipa_uc_shared_area - AP/microcontroller shared memory area
+ *
+ * @command: command code (AP->microcontroller)
+ * @command_param: low 32 bits of command parameter (AP->microcontroller)
+ * @command_param_hi: high 32 bits of command parameter (AP->microcontroller)
+ *
+ * @response: response code (microcontroller->AP)
+ * @response_param: response parameter (microcontroller->AP)
+ *
+ * @event: event code (microcontroller->AP)
+ * @event_param: event parameter (microcontroller->AP)
+ *
+ * @first_error_address: address of first error-source on SNOC
+ * @hw_state: state of hardware (including error type information)
+ * @warning_counter: counter of non-fatal hardware errors
+ * @interface_version: hardware-reported interface version
+ */
+struct ipa_uc_shared_area {
+	u32 command		: 8;	/* enum ipa_uc_command */
+	/* 3 reserved bytes */
+	u32 command_param;
+	u32 command_param_hi;
+
+	u32 response		: 8; 	/* enum ipa_uc_response */
+	/* 3 reserved bytes */
+	u32 response_param;
+
+	u32 event		: 8;	/* enum ipa_uc_event */
+	/* 3 reserved bytes */
+	u32 event_param;
+
+	u32 first_error_address;
+	u32 hw_state		: 8,
+	    warning_counter	: 8,
+	    reserved		: 16;
+	u32 interface_version	: 16;
+	/* 2 reserved bytes */
+};
+
+/** struct ipa_uc_ctx - IPA microcontroller context
+ *
+ * @uc_loaded: whether microcontroller has been loaded
+ * @shared: pointer to AP/microcontroller shared memory area
+ */
+struct ipa_uc_ctx {
+	bool uc_loaded;
+	struct ipa_uc_shared_area *shared;
+} ipa_uc_ctx;
+
+/*
+ * Microcontroller event codes, error codes, commands, and responses
+ * to commands all encode both a "code" and a "feature" in their
+ * 8-bit numeric value.  The top 3 bits represent the feature, and
+ * the bottom 5 bits represent the code.  A "common" feature uses
+ * feature code 0, and at this time we only deal with common
+ * features.  Because of this we can just ignore the feature bits
+ * and define the values of symbols in  the following enumerated
+ * types by just their code values.
+ */
+
+/** enum ipa_uc_event - common cpu events (microcontroller->AP)
+ *
+ * @IPA_UC_EVENT_NO_OP: no event present
+ * @IPA_UC_EVENT_ERROR: system error has been detected
+ * @IPA_UC_EVENT_LOG_INFO: logging information available
+ */
+enum ipa_uc_event {
+	IPA_UC_EVENT_NO_OP     = 0,
+	IPA_UC_EVENT_ERROR     = 1,
+	IPA_UC_EVENT_LOG_INFO  = 2,
+};
+
+/** enum ipa_uc_error - common error types (microcontroller->AP)
+ *
+ * @IPA_UC_ERROR_NONE: no error
+ * @IPA_UC_ERROR_INVALID_DOORBELL: invalid data read from doorbell
+ * @IPA_UC_ERROR_DMA: unexpected DMA error
+ * @IPA_UC_ERROR_FATAL_SYSTEM: microcontroller has crashed and requires reset
+ * @IPA_UC_ERROR_INVALID_OPCODE: invalid opcode sent
+ * @IPA_UC_ERROR_INVALID_PARAMS: invalid params for the requested command
+ * @IPA_UC_ERROR_CONS_DISABLE_CMD_GSI_STOP: consumer endpoint stop failure
+ * @IPA_UC_ERROR_PROD_DISABLE_CMD_GSI_STOP: producer endpoint stop failure
+ * @IPA_UC_ERROR_CH_NOT_EMPTY: micrcontroller GSI channel is not empty
+ */
+enum ipa_uc_error {
+	IPA_UC_ERROR_NONE			= 0,
+	IPA_UC_ERROR_INVALID_DOORBELL		= 1,
+	IPA_UC_ERROR_DMA			= 2,
+	IPA_UC_ERROR_FATAL_SYSTEM		= 3,
+	IPA_UC_ERROR_INVALID_OPCODE		= 4,
+	IPA_UC_ERROR_INVALID_PARAMS		= 5,
+	IPA_UC_ERROR_CONS_DISABLE_CMD_GSI_STOP	= 6,
+	IPA_UC_ERROR_PROD_DISABLE_CMD_GSI_STOP	= 7,
+	IPA_UC_ERROR_CH_NOT_EMPTY		= 8,
+};
+
+/** enum ipa_uc_command - commands from the AP to the microcontroller
+ *
+ * @IPA_UC_COMMAND_NO_OP: no operation
+ * @IPA_UC_COMMAND_UPDATE_FLAGS: request to re-read configuration flags
+ * @IPA_UC_COMMAND_DEBUG_RUN_TEST: request to run hardware test
+ * @IPA_UC_COMMAND_DEBUG_GET_INFO: request to read internal debug information
+ * @IPA_UC_COMMAND_ERR_FATAL: AP system crash notification
+ * @IPA_UC_COMMAND_CLK_GATE: request hardware to enter clock gated state
+ * @IPA_UC_COMMAND_CLK_UNGATE: request hardware to enter clock ungated state
+ * @IPA_UC_COMMAND_MEMCPY: request hardware to perform memcpy
+ * @IPA_UC_COMMAND_RESET_PIPE: request endpoint reset
+ * @IPA_UC_COMMAND_REG_WRITE: request a register be written
+ * @IPA_UC_COMMAND_GSI_CH_EMPTY: request to determine whether channel is empty
+ */
+enum ipa_uc_command {
+	IPA_UC_COMMAND_NO_OP		= 0,
+	IPA_UC_COMMAND_UPDATE_FLAGS	= 1,
+	IPA_UC_COMMAND_DEBUG_RUN_TEST	= 2,
+	IPA_UC_COMMAND_DEBUG_GET_INFO	= 3,
+	IPA_UC_COMMAND_ERR_FATAL	= 4,
+	IPA_UC_COMMAND_CLK_GATE		= 5,
+	IPA_UC_COMMAND_CLK_UNGATE	= 6,
+	IPA_UC_COMMAND_MEMCPY		= 7,
+	IPA_UC_COMMAND_RESET_PIPE	= 8,
+	IPA_UC_COMMAND_REG_WRITE	= 9,
+	IPA_UC_COMMAND_GSI_CH_EMPTY	= 10,
+};
+
+/** enum ipa_uc_response - common hardware response codes
+ *
+ * @IPA_UC_RESPONSE_NO_OP: no operation
+ * @IPA_UC_RESPONSE_INIT_COMPLETED: microcontroller ready
+ * @IPA_UC_RESPONSE_CMD_COMPLETED: AP-issued command has completed
+ * @IPA_UC_RESPONSE_DEBUG_GET_INFO: get debug info
+ */
+enum ipa_uc_response {
+	IPA_UC_RESPONSE_NO_OP		= 0,
+	IPA_UC_RESPONSE_INIT_COMPLETED	= 1,
+	IPA_UC_RESPONSE_CMD_COMPLETED	= 2,
+	IPA_UC_RESPONSE_DEBUG_GET_INFO	= 3,
+};
+
+/** union ipa_uc_event_data - microcontroller->AP event data
+ *
+ * @error_type: ipa_uc_error error type value
+ * @raw32b: 32-bit register value (used when reading)
+ */
+union ipa_uc_event_data {
+	u8 error_type;	/* enum ipa_uc_error */
+	u32 raw32b;
+} __packed;
+
+/** union ipa_uc_response_data - response to AP command
+ *
+ * @command: the AP issued command this is responding to
+ * @status: 0 for success indication, otherwise failure
+ * @raw32b: 32-bit register value (used when reading)
+ */
+union ipa_uc_response_data {
+	struct ipa_uc_response_param {
+		u8 command;	/* enum ipa_uc_command */
+		u8 status;	/* enum ipa_uc_error */
+	} params;
+	u32 raw32b;
+} __packed;
+
+/** ipa_uc_loaded() - tell whether the microcontroller has been loaded
+ *
+ * Returns true if the microcontroller is loaded, false otherwise
+ */
+bool ipa_uc_loaded(void)
+{
+	return ipa_uc_ctx.uc_loaded;
+}
+
+static void
+ipa_uc_event_handler(enum ipa_irq_type interrupt, u32 interrupt_data)
+{
+	struct ipa_uc_shared_area *shared = ipa_uc_ctx.shared;
+	union ipa_uc_event_data event_param;
+	u8 event;
+
+	event = shared->event;
+	event_param.raw32b = shared->event_param;
+
+	/* General handling */
+	if (event == IPA_UC_EVENT_ERROR) {
+		ipa_err("uC error type 0x%02x timestamp 0x%08x\n",
+			event_param.error_type, ipa_read_reg(IPA_TAG_TIMER));
+		ipa_bug();
+	} else {
+		ipa_err("unsupported uC event opcode=%u\n", event);
+	}
+}
+
+static void
+ipa_uc_response_hdlr(enum ipa_irq_type interrupt, u32 interrupt_data)
+{
+	struct ipa_uc_shared_area *shared = ipa_uc_ctx.shared;
+	union ipa_uc_response_data response_data;
+	u8 response;
+
+	response = shared->response;
+
+	/* An INIT_COMPLETED response message is sent to the AP by
+	 * the microcontroller when it is operational.  Other than
+	 * this, the AP should only receive responses from the
+	 * microntroller when it has sent it a request message.
+	 */
+	if (response == IPA_UC_RESPONSE_INIT_COMPLETED) {
+		/* The proxy vote is held until uC is loaded to ensure that
+		 * IPA_HW_2_CPU_RESPONSE_INIT_COMPLETED is received.
+		 */
+		ipa_proxy_clk_unvote();
+		ipa_uc_ctx.uc_loaded = true;
+	} else if (response == IPA_UC_RESPONSE_CMD_COMPLETED) {
+		response_data.raw32b = shared->response_param;
+		ipa_err("uC command response code %u status %u\n",
+			response_data.params.command,
+			response_data.params.status);
+	} else {
+		ipa_err("Unsupported uC rsp opcode = %u\n", response);
+	}
+}
+
+/** ipa_uc_init() - Initialize the microcontroller
+ *
+ * Returns pointer to microcontroller context on success, NULL otherwise
+ */
+struct ipa_uc_ctx *ipa_uc_init(phys_addr_t phys_addr)
+{
+	phys_addr += ipa_reg_n_offset(IPA_SRAM_DIRECT_ACCESS_N, 0);
+	ipa_uc_ctx.shared = ioremap(phys_addr, IPA_RAM_UC_SMEM_SIZE);
+	if (!ipa_uc_ctx.shared)
+		return NULL;
+
+	ipa_add_interrupt_handler(IPA_UC_IRQ_0, ipa_uc_event_handler);
+	ipa_add_interrupt_handler(IPA_UC_IRQ_1, ipa_uc_response_hdlr);
+
+	return &ipa_uc_ctx;
+}
+
+/* Send a command to the microcontroller */
+static void send_uc_command(u32 command, u32 command_param)
+{
+	struct ipa_uc_shared_area *shared = ipa_uc_ctx.shared;
+
+	shared->command = command;
+	shared->command_param = command_param;
+	shared->command_param_hi = 0;
+	shared->response = 0;
+	shared->response_param = 0;
+
+	wmb();	/* ensure write to shared memory is done before triggering uc */
+
+	ipa_write_reg_n(IPA_IRQ_EE_UC_N, IPA_EE_AP, 0x1);
+}
+
+void ipa_uc_panic_notifier(void)
+{
+	if (!ipa_uc_ctx.uc_loaded)
+		return;
+
+	if (!ipa_client_add_additional())
+		return;
+
+	send_uc_command(IPA_UC_COMMAND_ERR_FATAL, 0);
+
+	/* give uc enough time to save state */
+	udelay(IPA_SEND_DELAY);
+
+	ipa_client_remove();
+}
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 06/12] soc: qcom: ipa: QMI modem communication
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

QMI is a mechanism that allows entities on the AP to communicate and
coordinate with peer entities on a modem.  Each peer can create an
endpoint for communicating with the other.  QMI defines a way for
the format of messages sent between endpoints to be described, and
uses a service to route and deliver these messages.

For IPA, QMI is used to synchronize the startup sequence of the IPA
drivers resident on the AP and modem.  The documentation in
"ipa_qmi.c" below provides more detail about the message interchange
involved.  The IPA QMI code is divided into the "main" code that
implements message handling and synchronization, and the message
code that defines the structure and layout of the messages used.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_qmi.c     | 406 +++++++++++++++++++++++
 drivers/net/ipa/ipa_qmi.h     |  12 +
 drivers/net/ipa/ipa_qmi_msg.c | 587 ++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_qmi_msg.h | 233 ++++++++++++++
 4 files changed, 1238 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_qmi.c
 create mode 100644 drivers/net/ipa/ipa_qmi.h
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.c
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.h

diff --git a/drivers/net/ipa/ipa_qmi.c b/drivers/net/ipa/ipa_qmi.c
new file mode 100644
index 000000000000..a19cfc4043d3
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/qrtr.h>
+#include <linux/soc/qcom/qmi.h>
+
+#include "ipa_qmi_msg.h"
+
+#include "ipa_i.h"	/* ipa_err() */
+
+#define QMI_INIT_DRIVER_TIMEOUT	60000	/* A minute in milliseconds */
+
+static bool ipa_qmi_initialized;
+
+/* The AP and modem perform a "handshake" at initialization time to
+ * ensure each side knows the other side is ready.  Two pairs of QMI
+ * handles (endpoints) are used for this; one provides service on
+ * the modem for AP requests, and the other is on the AP to service
+ * modem requests (and to supply an indication from the AP).
+ *
+ * The QMI service on the modem expects to receive an INIT_DRIVER
+ * request from the AP, which contains parameters used by the
+ * modem during initialization.  The AP sends this request as soon
+ * as it is knows the service is available.  The modem responds to
+ * this request immediately.
+ *
+ * When the modem learns the AP service is available, it is able
+ * to communicate its status to the AP.  The modem uses this to
+ * tell the AP when it is ready to receive an indication, sending
+ * an INDICATION_REGISTER request to the handle served by the AP.
+ * This is separate from the modem driver initialization.
+ *
+ * When the modem has completed the driver initialization requested
+ * by the AP, it sends a DRIVER_INIT_COMPLETE request to the AP.
+ * This request could arrive at the AP either before or after the
+ * INDICATION_REGISTER request.
+ *
+ * The final step in the handshake occurs after the AP has received
+ * both requests from the modem.  The AP completes the handshake by
+ * sending an INIT_COMPLETE_IND indication message to the modem.
+ */
+
+#define IPA_HOST_SERVICE_SVC_ID		0x31
+#define IPA_HOST_SVC_VERS		1
+#define IPA_HOST_SERVICE_INS_ID		1
+
+#define IPA_MODEM_SERVICE_SVC_ID	0x31
+#define IPA_MODEM_SERVICE_INS_ID	2
+#define IPA_MODEM_SVC_VERS		1
+
+/* Used to send an INIT_DRIVER request to the modem */
+static struct qmi_handle client_handle;
+
+/* Requests from the modem arrive on the server handle to tell us
+ * when it is prepared to receive an INIT_COMPLETE indication, and
+ * when its driver initialization is complete.  The AP sends the
+ * indication after it has received and responded to both requests.
+ */
+static struct qmi_handle server_handle;
+
+/* These track state during the handshake */
+static bool indication_register_received;
+static bool init_driver_response_received;
+
+/* Send an INIT_COMPLETE_IND indication message to the modem */
+static int ipa_send_master_driver_init_complete_ind(struct qmi_handle *qmi,
+						    struct sockaddr_qrtr *sq)
+{
+	struct ipa_init_complete_ind ind = { };
+
+	ind.status.result = QMI_RESULT_SUCCESS_V01;
+	ind.status.error = QMI_ERR_NONE_V01;
+
+	return qmi_send_indication(qmi, sq, IPA_QMI_INIT_COMPLETE_IND,
+				   IPA_QMI_INIT_COMPLETE_IND_SZ,
+				   ipa_init_complete_ind_ei, &ind);
+}
+
+/* This function is called to determine whether to complete the
+ * handshake by sending an INIT_COMPLETE_IND indication message to
+ * the modem.  The "init_driver" parameter is false when we've
+ * received an INDICATION_REGISTER request message from the modem,
+ * or true when we've received the response from the INIT_DRIVER
+ * request message we send.  If this function decides the message
+ * should be sent, it calls ipa_send_master_driver_init_complete_ind()
+ * to send it.
+ */
+static void ipa_handshake_complete(struct qmi_handle *qmi,
+				   struct sockaddr_qrtr *sq, bool init_driver)
+{
+	bool send_it;
+	int ret;
+
+	if (init_driver) {
+		init_driver_response_received = true;
+		send_it = indication_register_received;
+	} else {
+		indication_register_received = true;
+		send_it = init_driver_response_received;
+	}
+	if (!send_it)
+		return;
+
+	ret = ipa_send_master_driver_init_complete_ind(qmi, sq);
+	if (ret)
+		ipa_err("error %d sending init complete indication\n", ret);
+}
+
+/* Callback function to handle an INDICATION_REGISTER request message
+ * from the modem.  This informs the AP that the modem is now ready to
+ * receive the INIT_COMPLETE_IND indication message.
+ */
+static void ipa_indication_register_fn(struct qmi_handle *qmi,
+				       struct sockaddr_qrtr *sq,
+				       struct qmi_txn *txn,
+				       const void *decoded)
+{
+	struct ipa_indication_register_rsp rsp = { };
+	int ret;
+
+	rsp.rsp.result = QMI_RESULT_SUCCESS_V01;
+	rsp.rsp.error = QMI_ERR_NONE_V01;
+
+	ret = qmi_send_response(qmi, sq, txn, IPA_QMI_INDICATION_REGISTER,
+				IPA_QMI_INDICATION_REGISTER_RSP_SZ,
+				ipa_indication_register_rsp_ei, &rsp);
+	if (ret)
+		ipa_err("error %d sending response\n", ret);
+	else
+		ipa_handshake_complete(qmi, sq, false);
+}
+
+/* Callback function to handle a DRIVER_INIT_COMPLETE request message
+ * from the modem.  This informs the AP that the modem has completed
+ * the initializion of its driver.
+ */
+static void ipa_driver_init_complete_fn(struct qmi_handle *qmi,
+					struct sockaddr_qrtr *sq,
+					struct qmi_txn *txn,
+					const void *decoded)
+{
+	struct ipa_driver_init_complete_rsp rsp = { };
+	int ret;
+
+	rsp.rsp.result = QMI_RESULT_SUCCESS_V01;
+	rsp.rsp.error = QMI_ERR_NONE_V01;
+
+	ret = qmi_send_response(qmi, sq, txn, IPA_QMI_DRIVER_INIT_COMPLETE,
+				IPA_QMI_DRIVER_INIT_COMPLETE_RSP_SZ,
+				ipa_driver_init_complete_rsp_ei, &rsp);
+	if (ret)
+		ipa_err("error %d sending response\n", ret);
+}
+
+/* The server handles two request message types sent by the modem. */
+static struct qmi_msg_handler ipa_server_msg_handlers[] = {
+	{
+		.type		= QMI_REQUEST,
+		.msg_id		= IPA_QMI_INDICATION_REGISTER,
+		.ei		= ipa_indication_register_req_ei,
+		.decoded_size	= IPA_QMI_INDICATION_REGISTER_REQ_SZ,
+		.fn		= ipa_indication_register_fn,
+	},
+	{
+		.type		= QMI_REQUEST,
+		.msg_id		= IPA_QMI_DRIVER_INIT_COMPLETE,
+		.ei		= ipa_driver_init_complete_req_ei,
+		.decoded_size	= IPA_QMI_DRIVER_INIT_COMPLETE_REQ_SZ,
+		.fn		= ipa_driver_init_complete_fn,
+	},
+};
+
+/* Callback function to handle an IPA_QMI_INIT_DRIVER response message
+ * from the modem.  This only acknowledges that the modem received the
+ * request.  The modem will eventually report that it has completed its
+ * modem initialization by sending a IPA_QMI_DRIVER_INIT_COMPLETE request.
+ */
+static void ipa_init_driver_rsp_fn(struct qmi_handle *qmi,
+				   struct sockaddr_qrtr *sq,
+				   struct qmi_txn *txn,
+				   const void *decoded)
+{
+	txn->result = 0;	/* IPA_QMI_INIT_DRIVER request was successful */
+	complete(&txn->completion);
+
+	ipa_handshake_complete(qmi, sq, true);
+}
+
+/* The client handles one response message type sent by the modem. */
+static struct qmi_msg_handler ipa_client_msg_handlers[] = {
+	{
+		.type		= QMI_RESPONSE,
+		.msg_id		= IPA_QMI_INIT_DRIVER,
+		.ei		= ipa_init_modem_driver_rsp_ei,
+		.decoded_size	= IPA_QMI_INIT_DRIVER_RSP_SZ,
+		.fn		= ipa_init_driver_rsp_fn,
+	},
+};
+
+/* Return a pointer to an init modem driver request structure, which
+ * contains configuration parameters for the modem.  The modem may
+ * be started multiple times, but generally these parameters don't
+ * change so we can reuse the request structure once it's initialized.
+ * The only exception is the skip_uc_load field, which will be set
+ * only after the microcontroller has reported it has completed its
+ * initialization.
+ */
+static const struct ipa_init_modem_driver_req *init_modem_driver_req(void)
+{
+	static struct ipa_init_modem_driver_req req;
+	u32 base;
+
+	/* This is not the first boot if the microcontroller is loaded */
+	req.skip_uc_load = ipa_uc_loaded() ? 1 : 0;
+	req.skip_uc_load_valid = true;
+
+	/* We only have to initialize most of it once */
+	if (req.platform_type_valid)
+		return &req;
+
+	/* All offsets are relative to the start of IPA shared memory */
+	base = (u32)ipa_ctx->smem_offset;
+
+	req.platform_type_valid = true;
+	req.platform_type = IPA_QMI_PLATFORM_TYPE_MSM_ANDROID;
+
+	req.hdr_tbl_info_valid = IPA_MEM_MODEM_HDR_SIZE ? 1 : 0;
+	req.hdr_tbl_info.start = base + IPA_MEM_MODEM_HDR_OFST;
+	req.hdr_tbl_info.end = req.hdr_tbl_info.start +
+					IPA_MEM_MODEM_HDR_SIZE - 1;
+
+	req.v4_route_tbl_info_valid = true;
+	req.v4_route_tbl_info.start = base + IPA_MEM_V4_RT_NHASH_OFST;
+	req.v4_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v6_route_tbl_info_valid = true;
+	req.v6_route_tbl_info.start = base + IPA_MEM_V6_RT_NHASH_OFST;
+	req.v6_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v4_filter_tbl_start_valid = true;
+	req.v4_filter_tbl_start = base + IPA_MEM_V4_FLT_NHASH_OFST;
+
+	req.v6_filter_tbl_start_valid = true;
+	req.v6_filter_tbl_start = base + IPA_MEM_V6_FLT_NHASH_OFST;
+
+	req.modem_mem_info_valid = IPA_MEM_MODEM_SIZE ? 1 : 0;
+	req.modem_mem_info.start = base + IPA_MEM_MODEM_OFST;
+	req.modem_mem_info.size = IPA_MEM_MODEM_SIZE;
+
+	req.ctrl_comm_dest_end_pt_valid = true;
+	req.ctrl_comm_dest_end_pt = ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS);
+
+	req.hdr_proc_ctx_tbl_info_valid =
+			IPA_MEM_MODEM_HDR_PROC_CTX_SIZE ? 1 : 0;
+	req.hdr_proc_ctx_tbl_info.start =
+			base + IPA_MEM_MODEM_HDR_PROC_CTX_OFST;
+	req.hdr_proc_ctx_tbl_info.end = req.hdr_proc_ctx_tbl_info.start +
+			IPA_MEM_MODEM_HDR_PROC_CTX_SIZE - 1;
+
+	req.v4_hash_route_tbl_info_valid = true;
+	req.v4_hash_route_tbl_info.start = base + IPA_MEM_V4_RT_HASH_OFST;
+	req.v4_hash_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v6_hash_route_tbl_info_valid = true;
+	req.v6_hash_route_tbl_info.start = base + IPA_MEM_V6_RT_HASH_OFST;
+	req.v6_hash_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v4_hash_filter_tbl_start_valid = true;
+	req.v4_hash_filter_tbl_start = base + IPA_MEM_V4_FLT_HASH_OFST;
+
+	req.v6_hash_filter_tbl_start_valid = true;
+	req.v6_hash_filter_tbl_start = base + IPA_MEM_V6_FLT_HASH_OFST;
+
+	return &req;
+}
+
+/* The modem service we requested is now available via the client
+ * handle.  Send an INIT_DRIVER request to the modem.
+ */
+static int
+ipa_client_new_server(struct qmi_handle *qmi, struct qmi_service *svc)
+{
+	const struct ipa_init_modem_driver_req *req = init_modem_driver_req();
+	struct sockaddr_qrtr sq;
+	struct qmi_txn *txn;
+	int ret;
+
+	txn = kzalloc(sizeof(*txn), GFP_KERNEL);
+	if (!txn)
+		return -ENOMEM;
+
+	ret = qmi_txn_init(qmi, txn, NULL, NULL);
+	if (ret) {
+		kfree(txn);
+		return ret;
+	}
+
+	sq.sq_family = AF_QIPCRTR;
+	sq.sq_node = svc->node;
+	sq.sq_port = svc->port;
+
+	ret = qmi_send_request(qmi, &sq, txn, IPA_QMI_INIT_DRIVER,
+			       IPA_QMI_INIT_DRIVER_REQ_SZ,
+			       ipa_init_modem_driver_req_ei, req);
+	if (!ret)
+		ret = qmi_txn_wait(txn, MAX_SCHEDULE_TIMEOUT);
+	if (ret)
+		qmi_txn_cancel(txn);
+	kfree(txn);
+
+	return ret;
+}
+
+/* The only callback we supply for the client handle is notification
+ * that the service on the modem has become available.
+ */
+static struct qmi_ops ipa_client_ops = {
+	.new_server	= ipa_client_new_server,
+};
+
+static int ipa_qmi_initialize(void)
+{
+	int ret;
+
+	/* The only handle operation that might be interesting for the
+	 * server would be del_client, to find out when the modem side
+	 * client has disappeared.  But other than reporting the event,
+	 * we wouldn't do anything about that.  So we just pass a null
+	 * pointer for its handle operations.  All the real work is
+	 * done by the message handlers.
+	 */
+	ret = qmi_handle_init(&server_handle, IPA_QMI_SERVER_MAX_RCV_SZ,
+			      NULL, ipa_server_msg_handlers);
+	if (ret < 0)
+		return ret;
+
+	ret = qmi_add_server(&server_handle, IPA_HOST_SERVICE_SVC_ID,
+			     IPA_HOST_SVC_VERS, IPA_HOST_SERVICE_INS_ID);
+	if (ret < 0)
+		goto err_release_server_handle;
+
+	/* The client handle is only used for sending an INIT_DRIVER
+	 * request to the modem, and receiving its response message.
+	 */
+	ret = qmi_handle_init(&client_handle, IPA_QMI_CLIENT_MAX_RCV_SZ,
+			      &ipa_client_ops, ipa_client_msg_handlers);
+	if (ret < 0)
+		goto err_release_server_handle;
+
+	ret = qmi_add_lookup(&client_handle, IPA_MODEM_SERVICE_SVC_ID,
+			     IPA_MODEM_SVC_VERS, IPA_MODEM_SERVICE_INS_ID);
+	if (ret < 0)
+		goto err_release_client_handle;
+
+	ipa_qmi_initialized = true;
+
+	return 0;
+
+err_release_client_handle:
+	/* Releasing the handle also removes registered lookups */
+	qmi_handle_release(&client_handle);
+	memset(&client_handle, 0, sizeof(client_handle));
+err_release_server_handle:
+	/* Releasing the handle also removes registered services */
+	qmi_handle_release(&server_handle);
+	memset(&server_handle, 0, sizeof(server_handle));
+
+	return ret;
+}
+
+/* This is called by the rmnet probe routine.  The rmnet driver can
+ * be unregistered after it has been initialized as a result of a
+ * subsystem shutdown; it can later be registered again if a
+ * subsystem restart occurs.  This function can therefore be called
+ * more than once.
+ */
+int ipa_qmi_init(void)
+{
+	init_driver_response_received = false;
+	indication_register_received = false;
+
+	if (!ipa_qmi_initialized)
+		return ipa_qmi_initialize();
+
+	return 0;
+}
+
+void ipa_qmi_exit(void)
+{
+	if (!ipa_qmi_initialized)
+		return;
+
+	qmi_handle_release(&client_handle);
+	memset(&client_handle, 0, sizeof(client_handle));
+
+	qmi_handle_release(&server_handle);
+	memset(&server_handle, 0, sizeof(server_handle));
+
+	ipa_qmi_initialized = false;
+}
diff --git a/drivers/net/ipa/ipa_qmi.h b/drivers/net/ipa/ipa_qmi.h
new file mode 100644
index 000000000000..3c03ff5c0454
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_QMI_H_
+#define _IPA_QMI_H_
+
+int ipa_qmi_init(void);
+void ipa_qmi_exit(void);
+
+#endif /* !_IPA_QMI_H_ */
diff --git a/drivers/net/ipa/ipa_qmi_msg.c b/drivers/net/ipa/ipa_qmi_msg.c
new file mode 100644
index 000000000000..c5347c63ef41
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi_msg.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#include <linux/stddef.h>
+#include <linux/soc/qcom/qmi.h>
+
+#include "ipa_qmi_msg.h"
+
+#ifndef sizeof_field
+#define sizeof_field(TYPE, MEMBER)	sizeof(((TYPE *)0)->MEMBER)
+#endif /* !sizeof_field */
+
+/* QMI message structure definition for struct ipa_indication_register_req */
+struct qmi_elem_info ipa_indication_register_req_ei[] = {
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     master_driver_init_complete_valid),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   master_driver_init_complete_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     master_driver_init_complete),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   master_driver_init_complete),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     data_usage_quota_reached_valid),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   data_usage_quota_reached_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     data_usage_quota_reached),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   data_usage_quota_reached),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_indication_register_rsp */
+struct qmi_elem_info ipa_indication_register_rsp_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_rsp,
+				     rsp),
+		.tlv_type	= 0x02,
+		.offset		= offsetof(struct ipa_indication_register_rsp,
+					   rsp),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_driver_init_complete_req */
+struct qmi_elem_info ipa_driver_init_complete_req_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_driver_init_complete_req,
+				     status),
+		.tlv_type	= 0x01,
+		.offset		= offsetof(struct ipa_driver_init_complete_req,
+					   status),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_driver_init_complete_rsp */
+struct qmi_elem_info ipa_driver_init_complete_rsp_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_driver_init_complete_rsp,
+				     rsp),
+		.tlv_type	= 0x02,
+		.elem_size	= offsetof(struct ipa_driver_init_complete_rsp,
+					   rsp),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_init_complete_ind */
+struct qmi_elem_info ipa_init_complete_ind_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_complete_ind,
+				     status),
+		.tlv_type	= 0x02,
+		.elem_size	= offsetof(struct ipa_init_complete_ind,
+					   status),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_mem_bounds */
+struct qmi_elem_info ipa_mem_bounds_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_bounds, start),
+		.offset		= offsetof(struct ipa_mem_bounds, start),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_bounds, end),
+		.offset		= offsetof(struct ipa_mem_bounds, end),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_mem_array */
+struct qmi_elem_info ipa_mem_array_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_array, start),
+		.offset		= offsetof(struct ipa_mem_array, start),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_array, count),
+		.offset		= offsetof(struct ipa_mem_array, count),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_mem_range */
+struct qmi_elem_info ipa_mem_range_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_range, start),
+		.offset		= offsetof(struct ipa_mem_range, start),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_range, size),
+		.offset		= offsetof(struct ipa_mem_range, size),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_init_modem_driver_req */
+struct qmi_elem_info ipa_init_modem_driver_req_ei[] = {
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     platform_type_valid),
+		.tlv_type	= 0x10,
+		.elem_size	= offsetof(struct ipa_init_modem_driver_req,
+					   platform_type_valid),
+	},
+	{
+		.data_type	= QMI_SIGNED_4_BYTE_ENUM,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     platform_type),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   platform_type),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_tbl_info_valid),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_tbl_info),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_tbl_info),
+		.ei_array	= ipa_mem_bounds_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_route_tbl_info_valid),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_route_tbl_info),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_route_tbl_info_valid),
+		.tlv_type	= 0x13,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_route_tbl_info),
+		.tlv_type	= 0x13,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_filter_tbl_start_valid),
+		.tlv_type	= 0x14,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_filter_tbl_start),
+		.tlv_type	= 0x14,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_filter_tbl_start_valid),
+		.tlv_type	= 0x15,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_filter_tbl_start),
+		.tlv_type	= 0x15,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     modem_mem_info_valid),
+		.tlv_type	= 0x16,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   modem_mem_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     modem_mem_info),
+		.tlv_type	= 0x16,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   modem_mem_info),
+		.ei_array	= ipa_mem_range_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     ctrl_comm_dest_end_pt_valid),
+		.tlv_type	= 0x17,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   ctrl_comm_dest_end_pt_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     ctrl_comm_dest_end_pt),
+		.tlv_type	= 0x17,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   ctrl_comm_dest_end_pt),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     skip_uc_load_valid),
+		.tlv_type	= 0x18,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   skip_uc_load_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     skip_uc_load),
+		.tlv_type	= 0x18,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   skip_uc_load),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_proc_ctx_tbl_info_valid),
+		.tlv_type	= 0x19,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_proc_ctx_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_proc_ctx_tbl_info),
+		.tlv_type	= 0x19,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_proc_ctx_tbl_info),
+		.ei_array	= ipa_mem_bounds_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     zip_tbl_info_valid),
+		.tlv_type	= 0x1a,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   zip_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     zip_tbl_info),
+		.tlv_type	= 0x1a,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   zip_tbl_info),
+		.ei_array	= ipa_mem_bounds_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_route_tbl_info_valid),
+		.tlv_type	= 0x1b,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_route_tbl_info),
+		.tlv_type	= 0x1b,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_route_tbl_info_valid),
+		.tlv_type	= 0x1c,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_route_tbl_info),
+		.tlv_type	= 0x1c,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_filter_tbl_start_valid),
+		.tlv_type	= 0x1d,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_filter_tbl_start),
+		.tlv_type	= 0x1d,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_filter_tbl_start_valid),
+		.tlv_type	= 0x1e,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_filter_tbl_start),
+		.tlv_type	= 0x1e,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_init_modem_driver_rsp */
+struct qmi_elem_info ipa_init_modem_driver_rsp_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     rsp),
+		.tlv_type	= 0x02,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   rsp),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     ctrl_comm_dest_end_pt_valid),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   ctrl_comm_dest_end_pt_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     ctrl_comm_dest_end_pt),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   ctrl_comm_dest_end_pt),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     default_end_pt_valid),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   default_end_pt_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     default_end_pt),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   default_end_pt),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     modem_driver_init_pending_valid),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   modem_driver_init_pending_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     modem_driver_init_pending),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   modem_driver_init_pending),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
diff --git a/drivers/net/ipa/ipa_qmi_msg.h b/drivers/net/ipa/ipa_qmi_msg.h
new file mode 100644
index 000000000000..009ce65ea114
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi_msg.h
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_QMI_MSG_H_
+#define _IPA_QMI_MSG_H_
+
+/* Request/response/indication QMI message ids used for IPA.  Receiving
+ * end issues a response for requests; indications require no response.
+ */
+#define IPA_QMI_INDICATION_REGISTER	0x20	/* modem -> AP request */
+#define IPA_QMI_INIT_DRIVER		0x21	/* AP -> modem request */
+#define IPA_QMI_INIT_COMPLETE_IND	0x22	/* AP -> modem indication */
+#define IPA_QMI_DRIVER_INIT_COMPLETE	0x35	/* modem -> AP request */
+
+/* The maximum size required for message types.  These sizes include
+ * the message data, along with type (1 byte) and length (2 byte)
+ * information for each field.  The qmi_send_*() interfaces require
+ * the message size to be provided.
+ */
+#define IPA_QMI_INDICATION_REGISTER_REQ_SZ	8	/* -> server handle */
+#define IPA_QMI_INDICATION_REGISTER_RSP_SZ	7	/* <- server handle */
+#define IPA_QMI_INIT_DRIVER_REQ_SZ		134	/* client handle -> */
+#define IPA_QMI_INIT_DRIVER_RSP_SZ		25	/* client handle <- */
+#define IPA_QMI_INIT_COMPLETE_IND_SZ		7	/* server handle -> */
+#define IPA_QMI_DRIVER_INIT_COMPLETE_REQ_SZ	4	/* -> server handle */
+#define IPA_QMI_DRIVER_INIT_COMPLETE_RSP_SZ	7	/* <- server handle */
+
+/* Maximum size of messages we expect the AP to receive (max of above) */
+#define IPA_QMI_SERVER_MAX_RCV_SZ		8
+#define IPA_QMI_CLIENT_MAX_RCV_SZ		25
+
+/* Request message for the IPA_QMI_INDICATION_REGISTER request */
+struct ipa_indication_register_req {
+	u8 master_driver_init_complete_valid;
+	u8 master_driver_init_complete;
+	u8 data_usage_quota_reached_valid;
+	u8 data_usage_quota_reached;
+};
+
+/* The response to a IPA_QMI_INDICATION_REGISTER request consists only of
+ * a standard QMI response.
+ */
+struct ipa_indication_register_rsp {
+	struct qmi_response_type_v01 rsp;
+};
+
+/* Request message for the IPA_QMI_DRIVER_INIT_COMPLETE request */
+struct ipa_driver_init_complete_req {
+	u8 status;
+};
+
+/* The response to a IPA_QMI_DRIVER_INIT_COMPLETE request consists only
+ * of a standard QMI response.
+ */
+struct ipa_driver_init_complete_rsp {
+	struct qmi_response_type_v01 rsp;
+};
+
+/* The message for the IPA_QMI_INIT_COMPLETE_IND indication consists
+ * only of a standard QMI response.
+ */
+struct ipa_init_complete_ind {
+	struct qmi_response_type_v01 status;
+};
+
+/* The AP tells the modem its platform type.  We assume Android. */
+enum ipa_platform_type {
+	IPA_QMI_PLATFORM_TYPE_INVALID		= 0,	/* Invalid */
+	IPA_QMI_PLATFORM_TYPE_TN		= 1,	/* Data card */
+	IPA_QMI_PLATFORM_TYPE_LE		= 2,	/* Data router */
+	IPA_QMI_PLATFORM_TYPE_MSM_ANDROID	= 3,	/* Android MSM */
+	IPA_QMI_PLATFORM_TYPE_MSM_WINDOWS	= 4,	/* Windows MSM */
+	IPA_QMI_PLATFORM_TYPE_MSM_QNX_V01	= 5,	/* QNX MSM */
+};
+
+/* This defines the start and end offset of a range of memory.  Both
+ * fields are offsets relative to the start of IPA shared memory.
+ * The end value is the last addressable byte *within* the range.
+ */
+struct ipa_mem_bounds {
+	u32 start;
+	u32 end;
+};
+
+/* This defines the location and size of an array.  The start value
+ * is an offset relative to the start of IPA shared memory.  The
+ * size of the array is implied by the number of entries (the entry
+ * size is assumed to be known).
+ */
+struct ipa_mem_array {
+	u32 start;
+	u32 count;
+};
+
+/* This defines the location and size of a range of memory.  The
+ * start is an offset relative to the start of IPA shared memory.
+ * This differs from the ipa_mem_bounds structure in that the size
+ * (in bytes) of the memory region is specified rather than the
+ * offset of its last byte.
+ */
+struct ipa_mem_range {
+	u32 start;
+	u32 size;
+};
+
+/* The message for the IPA_QMI_INIT_DRIVER request contains information
+ * from the AP that affects modem initialization.
+ */
+struct ipa_init_modem_driver_req {
+	u8			platform_type_valid;
+	u32			platform_type;	/* enum ipa_platform_type */
+
+	/* Modem header table information.  This defines the IPA shared
+	 * memory in which the modem may insert header table entries.
+	 */
+	u8			hdr_tbl_info_valid;
+	struct ipa_mem_bounds	hdr_tbl_info;
+
+	/* Routing table information.  These define the location and size of
+	 * non-hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_route_tbl_info_valid;
+	struct ipa_mem_array	v4_route_tbl_info;
+	u8			v6_route_tbl_info_valid;
+	struct ipa_mem_array	v6_route_tbl_info;
+
+	/* Filter table information.  These define the location and size of
+	 * non-hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_filter_tbl_start_valid;
+	u32			v4_filter_tbl_start;
+	u8			v6_filter_tbl_start_valid;
+	u32			v6_filter_tbl_start;
+
+	/* Modem memory information.  This defines the location and
+	 * size of memory available for the modem to use.
+	 */
+	u8			modem_mem_info_valid;
+	struct ipa_mem_range	modem_mem_info;
+
+	/* This defines the destination endpoint on the AP to which
+	 * the modem driver can send control commands.  IPA supports
+	 * 20 endpoints, so this must be 19 or less.
+	 */
+	u8			ctrl_comm_dest_end_pt_valid;
+	u32			ctrl_comm_dest_end_pt;
+
+	/* This defines whether the modem should load the microcontroller
+	 * or not.  It is unnecessary to reload it if the modem is being
+	 * restarted.
+	 *
+	 * NOTE: this field is named "is_ssr_bootup" elsewhere.
+	 */
+	u8			skip_uc_load_valid;
+	u8			skip_uc_load;
+
+	/* Processing context memory information.  This defines the memory in
+	 * which the modem may insert header processing context table entries.
+	 */
+	u8			hdr_proc_ctx_tbl_info_valid;
+	struct ipa_mem_bounds	hdr_proc_ctx_tbl_info;
+
+	/* Compression command memory information.  This defines the memory
+	 * in which the modem may insert compression/decompression commands.
+	 */
+	u8			zip_tbl_info_valid;
+	struct ipa_mem_bounds	zip_tbl_info;
+
+	/* Routing table information.  These define the location and size
+	 * of hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_hash_route_tbl_info_valid;
+	struct ipa_mem_array	v4_hash_route_tbl_info;
+	u8			v6_hash_route_tbl_info_valid;
+	struct ipa_mem_array	v6_hash_route_tbl_info;
+
+	/* Filter table information.  These define the location and size
+	 * of hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_hash_filter_tbl_start_valid;
+	u32			v4_hash_filter_tbl_start;
+	u8			v6_hash_filter_tbl_start_valid;
+	u32			v6_hash_filter_tbl_start;
+};
+
+/* The response to a IPA_QMI_INIT_DRIVER request begins with a standard
+ * QMI response, but contains other information as well.  Currently we
+ * simply wait for the the INIT_DRIVER transaction to complete and
+ * ignore any other data that might be returned.
+ */
+struct ipa_init_modem_driver_rsp {
+	struct qmi_response_type_v01	rsp;
+
+	/* This defines the destination endpoint on the modem to which
+	 * the AP driver can send control commands.  IPA supports
+	 * 20 endpoints, so this must be 19 or less.
+	 */
+	u8				ctrl_comm_dest_end_pt_valid;
+	u32				ctrl_comm_dest_end_pt;
+
+	/* This defines the default endpoint.  The AP driver is not
+	 * required to configure the hardware with this value.  IPA
+	 * supports 20 endpoints, so this must be 19 or less.
+	 */
+	u8				default_end_pt_valid;
+	u32				default_end_pt;
+
+	/* This defines whether a second handshake is required to complete
+	 * initialization.
+	 */
+	u8				modem_driver_init_pending_valid;
+	u8				modem_driver_init_pending;
+};
+
+/* Message structure definitions defined in "ipa_qmi_msg.c" */
+extern struct qmi_elem_info ipa_indication_register_req_ei[];
+extern struct qmi_elem_info ipa_indication_register_rsp_ei[];
+extern struct qmi_elem_info ipa_driver_init_complete_req_ei[];
+extern struct qmi_elem_info ipa_driver_init_complete_rsp_ei[];
+extern struct qmi_elem_info ipa_init_complete_ind_ei[];
+extern struct qmi_elem_info ipa_mem_bounds_ei[];
+extern struct qmi_elem_info ipa_mem_array_ei[];
+extern struct qmi_elem_info ipa_mem_range_ei[];
+extern struct qmi_elem_info ipa_init_modem_driver_req_ei[];
+extern struct qmi_elem_info ipa_init_modem_driver_rsp_ei[];
+
+#endif /* !_IPA_QMI_MSG_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 06/12] soc: qcom: ipa: QMI modem communication
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

QMI is a mechanism that allows entities on the AP to communicate and
coordinate with peer entities on a modem.  Each peer can create an
endpoint for communicating with the other.  QMI defines a way for
the format of messages sent between endpoints to be described, and
uses a service to route and deliver these messages.

For IPA, QMI is used to synchronize the startup sequence of the IPA
drivers resident on the AP and modem.  The documentation in
"ipa_qmi.c" below provides more detail about the message interchange
involved.  The IPA QMI code is divided into the "main" code that
implements message handling and synchronization, and the message
code that defines the structure and layout of the messages used.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_qmi.c     | 406 +++++++++++++++++++++++
 drivers/net/ipa/ipa_qmi.h     |  12 +
 drivers/net/ipa/ipa_qmi_msg.c | 587 ++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_qmi_msg.h | 233 ++++++++++++++
 4 files changed, 1238 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_qmi.c
 create mode 100644 drivers/net/ipa/ipa_qmi.h
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.c
 create mode 100644 drivers/net/ipa/ipa_qmi_msg.h

diff --git a/drivers/net/ipa/ipa_qmi.c b/drivers/net/ipa/ipa_qmi.c
new file mode 100644
index 000000000000..a19cfc4043d3
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/qrtr.h>
+#include <linux/soc/qcom/qmi.h>
+
+#include "ipa_qmi_msg.h"
+
+#include "ipa_i.h"	/* ipa_err() */
+
+#define QMI_INIT_DRIVER_TIMEOUT	60000	/* A minute in milliseconds */
+
+static bool ipa_qmi_initialized;
+
+/* The AP and modem perform a "handshake" at initialization time to
+ * ensure each side knows the other side is ready.  Two pairs of QMI
+ * handles (endpoints) are used for this; one provides service on
+ * the modem for AP requests, and the other is on the AP to service
+ * modem requests (and to supply an indication from the AP).
+ *
+ * The QMI service on the modem expects to receive an INIT_DRIVER
+ * request from the AP, which contains parameters used by the
+ * modem during initialization.  The AP sends this request as soon
+ * as it is knows the service is available.  The modem responds to
+ * this request immediately.
+ *
+ * When the modem learns the AP service is available, it is able
+ * to communicate its status to the AP.  The modem uses this to
+ * tell the AP when it is ready to receive an indication, sending
+ * an INDICATION_REGISTER request to the handle served by the AP.
+ * This is separate from the modem driver initialization.
+ *
+ * When the modem has completed the driver initialization requested
+ * by the AP, it sends a DRIVER_INIT_COMPLETE request to the AP.
+ * This request could arrive at the AP either before or after the
+ * INDICATION_REGISTER request.
+ *
+ * The final step in the handshake occurs after the AP has received
+ * both requests from the modem.  The AP completes the handshake by
+ * sending an INIT_COMPLETE_IND indication message to the modem.
+ */
+
+#define IPA_HOST_SERVICE_SVC_ID		0x31
+#define IPA_HOST_SVC_VERS		1
+#define IPA_HOST_SERVICE_INS_ID		1
+
+#define IPA_MODEM_SERVICE_SVC_ID	0x31
+#define IPA_MODEM_SERVICE_INS_ID	2
+#define IPA_MODEM_SVC_VERS		1
+
+/* Used to send an INIT_DRIVER request to the modem */
+static struct qmi_handle client_handle;
+
+/* Requests from the modem arrive on the server handle to tell us
+ * when it is prepared to receive an INIT_COMPLETE indication, and
+ * when its driver initialization is complete.  The AP sends the
+ * indication after it has received and responded to both requests.
+ */
+static struct qmi_handle server_handle;
+
+/* These track state during the handshake */
+static bool indication_register_received;
+static bool init_driver_response_received;
+
+/* Send an INIT_COMPLETE_IND indication message to the modem */
+static int ipa_send_master_driver_init_complete_ind(struct qmi_handle *qmi,
+						    struct sockaddr_qrtr *sq)
+{
+	struct ipa_init_complete_ind ind = { };
+
+	ind.status.result = QMI_RESULT_SUCCESS_V01;
+	ind.status.error = QMI_ERR_NONE_V01;
+
+	return qmi_send_indication(qmi, sq, IPA_QMI_INIT_COMPLETE_IND,
+				   IPA_QMI_INIT_COMPLETE_IND_SZ,
+				   ipa_init_complete_ind_ei, &ind);
+}
+
+/* This function is called to determine whether to complete the
+ * handshake by sending an INIT_COMPLETE_IND indication message to
+ * the modem.  The "init_driver" parameter is false when we've
+ * received an INDICATION_REGISTER request message from the modem,
+ * or true when we've received the response from the INIT_DRIVER
+ * request message we send.  If this function decides the message
+ * should be sent, it calls ipa_send_master_driver_init_complete_ind()
+ * to send it.
+ */
+static void ipa_handshake_complete(struct qmi_handle *qmi,
+				   struct sockaddr_qrtr *sq, bool init_driver)
+{
+	bool send_it;
+	int ret;
+
+	if (init_driver) {
+		init_driver_response_received = true;
+		send_it = indication_register_received;
+	} else {
+		indication_register_received = true;
+		send_it = init_driver_response_received;
+	}
+	if (!send_it)
+		return;
+
+	ret = ipa_send_master_driver_init_complete_ind(qmi, sq);
+	if (ret)
+		ipa_err("error %d sending init complete indication\n", ret);
+}
+
+/* Callback function to handle an INDICATION_REGISTER request message
+ * from the modem.  This informs the AP that the modem is now ready to
+ * receive the INIT_COMPLETE_IND indication message.
+ */
+static void ipa_indication_register_fn(struct qmi_handle *qmi,
+				       struct sockaddr_qrtr *sq,
+				       struct qmi_txn *txn,
+				       const void *decoded)
+{
+	struct ipa_indication_register_rsp rsp = { };
+	int ret;
+
+	rsp.rsp.result = QMI_RESULT_SUCCESS_V01;
+	rsp.rsp.error = QMI_ERR_NONE_V01;
+
+	ret = qmi_send_response(qmi, sq, txn, IPA_QMI_INDICATION_REGISTER,
+				IPA_QMI_INDICATION_REGISTER_RSP_SZ,
+				ipa_indication_register_rsp_ei, &rsp);
+	if (ret)
+		ipa_err("error %d sending response\n", ret);
+	else
+		ipa_handshake_complete(qmi, sq, false);
+}
+
+/* Callback function to handle a DRIVER_INIT_COMPLETE request message
+ * from the modem.  This informs the AP that the modem has completed
+ * the initializion of its driver.
+ */
+static void ipa_driver_init_complete_fn(struct qmi_handle *qmi,
+					struct sockaddr_qrtr *sq,
+					struct qmi_txn *txn,
+					const void *decoded)
+{
+	struct ipa_driver_init_complete_rsp rsp = { };
+	int ret;
+
+	rsp.rsp.result = QMI_RESULT_SUCCESS_V01;
+	rsp.rsp.error = QMI_ERR_NONE_V01;
+
+	ret = qmi_send_response(qmi, sq, txn, IPA_QMI_DRIVER_INIT_COMPLETE,
+				IPA_QMI_DRIVER_INIT_COMPLETE_RSP_SZ,
+				ipa_driver_init_complete_rsp_ei, &rsp);
+	if (ret)
+		ipa_err("error %d sending response\n", ret);
+}
+
+/* The server handles two request message types sent by the modem. */
+static struct qmi_msg_handler ipa_server_msg_handlers[] = {
+	{
+		.type		= QMI_REQUEST,
+		.msg_id		= IPA_QMI_INDICATION_REGISTER,
+		.ei		= ipa_indication_register_req_ei,
+		.decoded_size	= IPA_QMI_INDICATION_REGISTER_REQ_SZ,
+		.fn		= ipa_indication_register_fn,
+	},
+	{
+		.type		= QMI_REQUEST,
+		.msg_id		= IPA_QMI_DRIVER_INIT_COMPLETE,
+		.ei		= ipa_driver_init_complete_req_ei,
+		.decoded_size	= IPA_QMI_DRIVER_INIT_COMPLETE_REQ_SZ,
+		.fn		= ipa_driver_init_complete_fn,
+	},
+};
+
+/* Callback function to handle an IPA_QMI_INIT_DRIVER response message
+ * from the modem.  This only acknowledges that the modem received the
+ * request.  The modem will eventually report that it has completed its
+ * modem initialization by sending a IPA_QMI_DRIVER_INIT_COMPLETE request.
+ */
+static void ipa_init_driver_rsp_fn(struct qmi_handle *qmi,
+				   struct sockaddr_qrtr *sq,
+				   struct qmi_txn *txn,
+				   const void *decoded)
+{
+	txn->result = 0;	/* IPA_QMI_INIT_DRIVER request was successful */
+	complete(&txn->completion);
+
+	ipa_handshake_complete(qmi, sq, true);
+}
+
+/* The client handles one response message type sent by the modem. */
+static struct qmi_msg_handler ipa_client_msg_handlers[] = {
+	{
+		.type		= QMI_RESPONSE,
+		.msg_id		= IPA_QMI_INIT_DRIVER,
+		.ei		= ipa_init_modem_driver_rsp_ei,
+		.decoded_size	= IPA_QMI_INIT_DRIVER_RSP_SZ,
+		.fn		= ipa_init_driver_rsp_fn,
+	},
+};
+
+/* Return a pointer to an init modem driver request structure, which
+ * contains configuration parameters for the modem.  The modem may
+ * be started multiple times, but generally these parameters don't
+ * change so we can reuse the request structure once it's initialized.
+ * The only exception is the skip_uc_load field, which will be set
+ * only after the microcontroller has reported it has completed its
+ * initialization.
+ */
+static const struct ipa_init_modem_driver_req *init_modem_driver_req(void)
+{
+	static struct ipa_init_modem_driver_req req;
+	u32 base;
+
+	/* This is not the first boot if the microcontroller is loaded */
+	req.skip_uc_load = ipa_uc_loaded() ? 1 : 0;
+	req.skip_uc_load_valid = true;
+
+	/* We only have to initialize most of it once */
+	if (req.platform_type_valid)
+		return &req;
+
+	/* All offsets are relative to the start of IPA shared memory */
+	base = (u32)ipa_ctx->smem_offset;
+
+	req.platform_type_valid = true;
+	req.platform_type = IPA_QMI_PLATFORM_TYPE_MSM_ANDROID;
+
+	req.hdr_tbl_info_valid = IPA_MEM_MODEM_HDR_SIZE ? 1 : 0;
+	req.hdr_tbl_info.start = base + IPA_MEM_MODEM_HDR_OFST;
+	req.hdr_tbl_info.end = req.hdr_tbl_info.start +
+					IPA_MEM_MODEM_HDR_SIZE - 1;
+
+	req.v4_route_tbl_info_valid = true;
+	req.v4_route_tbl_info.start = base + IPA_MEM_V4_RT_NHASH_OFST;
+	req.v4_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v6_route_tbl_info_valid = true;
+	req.v6_route_tbl_info.start = base + IPA_MEM_V6_RT_NHASH_OFST;
+	req.v6_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v4_filter_tbl_start_valid = true;
+	req.v4_filter_tbl_start = base + IPA_MEM_V4_FLT_NHASH_OFST;
+
+	req.v6_filter_tbl_start_valid = true;
+	req.v6_filter_tbl_start = base + IPA_MEM_V6_FLT_NHASH_OFST;
+
+	req.modem_mem_info_valid = IPA_MEM_MODEM_SIZE ? 1 : 0;
+	req.modem_mem_info.start = base + IPA_MEM_MODEM_OFST;
+	req.modem_mem_info.size = IPA_MEM_MODEM_SIZE;
+
+	req.ctrl_comm_dest_end_pt_valid = true;
+	req.ctrl_comm_dest_end_pt = ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS);
+
+	req.hdr_proc_ctx_tbl_info_valid =
+			IPA_MEM_MODEM_HDR_PROC_CTX_SIZE ? 1 : 0;
+	req.hdr_proc_ctx_tbl_info.start =
+			base + IPA_MEM_MODEM_HDR_PROC_CTX_OFST;
+	req.hdr_proc_ctx_tbl_info.end = req.hdr_proc_ctx_tbl_info.start +
+			IPA_MEM_MODEM_HDR_PROC_CTX_SIZE - 1;
+
+	req.v4_hash_route_tbl_info_valid = true;
+	req.v4_hash_route_tbl_info.start = base + IPA_MEM_V4_RT_HASH_OFST;
+	req.v4_hash_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v6_hash_route_tbl_info_valid = true;
+	req.v6_hash_route_tbl_info.start = base + IPA_MEM_V6_RT_HASH_OFST;
+	req.v6_hash_route_tbl_info.count = IPA_MEM_MODEM_RT_COUNT;
+
+	req.v4_hash_filter_tbl_start_valid = true;
+	req.v4_hash_filter_tbl_start = base + IPA_MEM_V4_FLT_HASH_OFST;
+
+	req.v6_hash_filter_tbl_start_valid = true;
+	req.v6_hash_filter_tbl_start = base + IPA_MEM_V6_FLT_HASH_OFST;
+
+	return &req;
+}
+
+/* The modem service we requested is now available via the client
+ * handle.  Send an INIT_DRIVER request to the modem.
+ */
+static int
+ipa_client_new_server(struct qmi_handle *qmi, struct qmi_service *svc)
+{
+	const struct ipa_init_modem_driver_req *req = init_modem_driver_req();
+	struct sockaddr_qrtr sq;
+	struct qmi_txn *txn;
+	int ret;
+
+	txn = kzalloc(sizeof(*txn), GFP_KERNEL);
+	if (!txn)
+		return -ENOMEM;
+
+	ret = qmi_txn_init(qmi, txn, NULL, NULL);
+	if (ret) {
+		kfree(txn);
+		return ret;
+	}
+
+	sq.sq_family = AF_QIPCRTR;
+	sq.sq_node = svc->node;
+	sq.sq_port = svc->port;
+
+	ret = qmi_send_request(qmi, &sq, txn, IPA_QMI_INIT_DRIVER,
+			       IPA_QMI_INIT_DRIVER_REQ_SZ,
+			       ipa_init_modem_driver_req_ei, req);
+	if (!ret)
+		ret = qmi_txn_wait(txn, MAX_SCHEDULE_TIMEOUT);
+	if (ret)
+		qmi_txn_cancel(txn);
+	kfree(txn);
+
+	return ret;
+}
+
+/* The only callback we supply for the client handle is notification
+ * that the service on the modem has become available.
+ */
+static struct qmi_ops ipa_client_ops = {
+	.new_server	= ipa_client_new_server,
+};
+
+static int ipa_qmi_initialize(void)
+{
+	int ret;
+
+	/* The only handle operation that might be interesting for the
+	 * server would be del_client, to find out when the modem side
+	 * client has disappeared.  But other than reporting the event,
+	 * we wouldn't do anything about that.  So we just pass a null
+	 * pointer for its handle operations.  All the real work is
+	 * done by the message handlers.
+	 */
+	ret = qmi_handle_init(&server_handle, IPA_QMI_SERVER_MAX_RCV_SZ,
+			      NULL, ipa_server_msg_handlers);
+	if (ret < 0)
+		return ret;
+
+	ret = qmi_add_server(&server_handle, IPA_HOST_SERVICE_SVC_ID,
+			     IPA_HOST_SVC_VERS, IPA_HOST_SERVICE_INS_ID);
+	if (ret < 0)
+		goto err_release_server_handle;
+
+	/* The client handle is only used for sending an INIT_DRIVER
+	 * request to the modem, and receiving its response message.
+	 */
+	ret = qmi_handle_init(&client_handle, IPA_QMI_CLIENT_MAX_RCV_SZ,
+			      &ipa_client_ops, ipa_client_msg_handlers);
+	if (ret < 0)
+		goto err_release_server_handle;
+
+	ret = qmi_add_lookup(&client_handle, IPA_MODEM_SERVICE_SVC_ID,
+			     IPA_MODEM_SVC_VERS, IPA_MODEM_SERVICE_INS_ID);
+	if (ret < 0)
+		goto err_release_client_handle;
+
+	ipa_qmi_initialized = true;
+
+	return 0;
+
+err_release_client_handle:
+	/* Releasing the handle also removes registered lookups */
+	qmi_handle_release(&client_handle);
+	memset(&client_handle, 0, sizeof(client_handle));
+err_release_server_handle:
+	/* Releasing the handle also removes registered services */
+	qmi_handle_release(&server_handle);
+	memset(&server_handle, 0, sizeof(server_handle));
+
+	return ret;
+}
+
+/* This is called by the rmnet probe routine.  The rmnet driver can
+ * be unregistered after it has been initialized as a result of a
+ * subsystem shutdown; it can later be registered again if a
+ * subsystem restart occurs.  This function can therefore be called
+ * more than once.
+ */
+int ipa_qmi_init(void)
+{
+	init_driver_response_received = false;
+	indication_register_received = false;
+
+	if (!ipa_qmi_initialized)
+		return ipa_qmi_initialize();
+
+	return 0;
+}
+
+void ipa_qmi_exit(void)
+{
+	if (!ipa_qmi_initialized)
+		return;
+
+	qmi_handle_release(&client_handle);
+	memset(&client_handle, 0, sizeof(client_handle));
+
+	qmi_handle_release(&server_handle);
+	memset(&server_handle, 0, sizeof(server_handle));
+
+	ipa_qmi_initialized = false;
+}
diff --git a/drivers/net/ipa/ipa_qmi.h b/drivers/net/ipa/ipa_qmi.h
new file mode 100644
index 000000000000..3c03ff5c0454
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_QMI_H_
+#define _IPA_QMI_H_
+
+int ipa_qmi_init(void);
+void ipa_qmi_exit(void);
+
+#endif /* !_IPA_QMI_H_ */
diff --git a/drivers/net/ipa/ipa_qmi_msg.c b/drivers/net/ipa/ipa_qmi_msg.c
new file mode 100644
index 000000000000..c5347c63ef41
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi_msg.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#include <linux/stddef.h>
+#include <linux/soc/qcom/qmi.h>
+
+#include "ipa_qmi_msg.h"
+
+#ifndef sizeof_field
+#define sizeof_field(TYPE, MEMBER)	sizeof(((TYPE *)0)->MEMBER)
+#endif /* !sizeof_field */
+
+/* QMI message structure definition for struct ipa_indication_register_req */
+struct qmi_elem_info ipa_indication_register_req_ei[] = {
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     master_driver_init_complete_valid),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   master_driver_init_complete_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     master_driver_init_complete),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   master_driver_init_complete),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     data_usage_quota_reached_valid),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   data_usage_quota_reached_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_req,
+				     data_usage_quota_reached),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_indication_register_req,
+					   data_usage_quota_reached),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_indication_register_rsp */
+struct qmi_elem_info ipa_indication_register_rsp_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_indication_register_rsp,
+				     rsp),
+		.tlv_type	= 0x02,
+		.offset		= offsetof(struct ipa_indication_register_rsp,
+					   rsp),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_driver_init_complete_req */
+struct qmi_elem_info ipa_driver_init_complete_req_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_driver_init_complete_req,
+				     status),
+		.tlv_type	= 0x01,
+		.offset		= offsetof(struct ipa_driver_init_complete_req,
+					   status),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_driver_init_complete_rsp */
+struct qmi_elem_info ipa_driver_init_complete_rsp_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_driver_init_complete_rsp,
+				     rsp),
+		.tlv_type	= 0x02,
+		.elem_size	= offsetof(struct ipa_driver_init_complete_rsp,
+					   rsp),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_init_complete_ind */
+struct qmi_elem_info ipa_init_complete_ind_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_complete_ind,
+				     status),
+		.tlv_type	= 0x02,
+		.elem_size	= offsetof(struct ipa_init_complete_ind,
+					   status),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_mem_bounds */
+struct qmi_elem_info ipa_mem_bounds_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_bounds, start),
+		.offset		= offsetof(struct ipa_mem_bounds, start),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_bounds, end),
+		.offset		= offsetof(struct ipa_mem_bounds, end),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_mem_array */
+struct qmi_elem_info ipa_mem_array_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_array, start),
+		.offset		= offsetof(struct ipa_mem_array, start),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_array, count),
+		.offset		= offsetof(struct ipa_mem_array, count),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_mem_range */
+struct qmi_elem_info ipa_mem_range_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_range, start),
+		.offset		= offsetof(struct ipa_mem_range, start),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_mem_range, size),
+		.offset		= offsetof(struct ipa_mem_range, size),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_init_modem_driver_req */
+struct qmi_elem_info ipa_init_modem_driver_req_ei[] = {
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     platform_type_valid),
+		.tlv_type	= 0x10,
+		.elem_size	= offsetof(struct ipa_init_modem_driver_req,
+					   platform_type_valid),
+	},
+	{
+		.data_type	= QMI_SIGNED_4_BYTE_ENUM,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     platform_type),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   platform_type),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_tbl_info_valid),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_tbl_info),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_tbl_info),
+		.ei_array	= ipa_mem_bounds_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_route_tbl_info_valid),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_route_tbl_info),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_route_tbl_info_valid),
+		.tlv_type	= 0x13,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_route_tbl_info),
+		.tlv_type	= 0x13,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_filter_tbl_start_valid),
+		.tlv_type	= 0x14,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_filter_tbl_start),
+		.tlv_type	= 0x14,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_filter_tbl_start_valid),
+		.tlv_type	= 0x15,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_filter_tbl_start),
+		.tlv_type	= 0x15,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     modem_mem_info_valid),
+		.tlv_type	= 0x16,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   modem_mem_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     modem_mem_info),
+		.tlv_type	= 0x16,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   modem_mem_info),
+		.ei_array	= ipa_mem_range_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     ctrl_comm_dest_end_pt_valid),
+		.tlv_type	= 0x17,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   ctrl_comm_dest_end_pt_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     ctrl_comm_dest_end_pt),
+		.tlv_type	= 0x17,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   ctrl_comm_dest_end_pt),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     skip_uc_load_valid),
+		.tlv_type	= 0x18,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   skip_uc_load_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     skip_uc_load),
+		.tlv_type	= 0x18,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   skip_uc_load),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_proc_ctx_tbl_info_valid),
+		.tlv_type	= 0x19,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_proc_ctx_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hdr_proc_ctx_tbl_info),
+		.tlv_type	= 0x19,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hdr_proc_ctx_tbl_info),
+		.ei_array	= ipa_mem_bounds_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     zip_tbl_info_valid),
+		.tlv_type	= 0x1a,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   zip_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     zip_tbl_info),
+		.tlv_type	= 0x1a,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   zip_tbl_info),
+		.ei_array	= ipa_mem_bounds_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_route_tbl_info_valid),
+		.tlv_type	= 0x1b,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_route_tbl_info),
+		.tlv_type	= 0x1b,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_route_tbl_info_valid),
+		.tlv_type	= 0x1c,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_route_tbl_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_route_tbl_info),
+		.tlv_type	= 0x1c,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_route_tbl_info),
+		.ei_array	= ipa_mem_array_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_filter_tbl_start_valid),
+		.tlv_type	= 0x1d,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v4_hash_filter_tbl_start),
+		.tlv_type	= 0x1d,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v4_hash_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_filter_tbl_start_valid),
+		.tlv_type	= 0x1e,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_filter_tbl_start_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     v6_hash_filter_tbl_start),
+		.tlv_type	= 0x1e,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   v6_hash_filter_tbl_start),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
+/* QMI message structure definition for struct ipa_init_modem_driver_rsp */
+struct qmi_elem_info ipa_init_modem_driver_rsp_ei[] = {
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     rsp),
+		.tlv_type	= 0x02,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   rsp),
+		.ei_array	= qmi_response_type_v01_ei,
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     ctrl_comm_dest_end_pt_valid),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   ctrl_comm_dest_end_pt_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     ctrl_comm_dest_end_pt),
+		.tlv_type	= 0x10,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   ctrl_comm_dest_end_pt),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     default_end_pt_valid),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   default_end_pt_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     default_end_pt),
+		.tlv_type	= 0x11,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   default_end_pt),
+	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     modem_driver_init_pending_valid),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   modem_driver_init_pending_valid),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_rsp,
+				     modem_driver_init_pending),
+		.tlv_type	= 0x12,
+		.offset		= offsetof(struct ipa_init_modem_driver_rsp,
+					   modem_driver_init_pending),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
diff --git a/drivers/net/ipa/ipa_qmi_msg.h b/drivers/net/ipa/ipa_qmi_msg.h
new file mode 100644
index 000000000000..009ce65ea114
--- /dev/null
+++ b/drivers/net/ipa/ipa_qmi_msg.h
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_QMI_MSG_H_
+#define _IPA_QMI_MSG_H_
+
+/* Request/response/indication QMI message ids used for IPA.  Receiving
+ * end issues a response for requests; indications require no response.
+ */
+#define IPA_QMI_INDICATION_REGISTER	0x20	/* modem -> AP request */
+#define IPA_QMI_INIT_DRIVER		0x21	/* AP -> modem request */
+#define IPA_QMI_INIT_COMPLETE_IND	0x22	/* AP -> modem indication */
+#define IPA_QMI_DRIVER_INIT_COMPLETE	0x35	/* modem -> AP request */
+
+/* The maximum size required for message types.  These sizes include
+ * the message data, along with type (1 byte) and length (2 byte)
+ * information for each field.  The qmi_send_*() interfaces require
+ * the message size to be provided.
+ */
+#define IPA_QMI_INDICATION_REGISTER_REQ_SZ	8	/* -> server handle */
+#define IPA_QMI_INDICATION_REGISTER_RSP_SZ	7	/* <- server handle */
+#define IPA_QMI_INIT_DRIVER_REQ_SZ		134	/* client handle -> */
+#define IPA_QMI_INIT_DRIVER_RSP_SZ		25	/* client handle <- */
+#define IPA_QMI_INIT_COMPLETE_IND_SZ		7	/* server handle -> */
+#define IPA_QMI_DRIVER_INIT_COMPLETE_REQ_SZ	4	/* -> server handle */
+#define IPA_QMI_DRIVER_INIT_COMPLETE_RSP_SZ	7	/* <- server handle */
+
+/* Maximum size of messages we expect the AP to receive (max of above) */
+#define IPA_QMI_SERVER_MAX_RCV_SZ		8
+#define IPA_QMI_CLIENT_MAX_RCV_SZ		25
+
+/* Request message for the IPA_QMI_INDICATION_REGISTER request */
+struct ipa_indication_register_req {
+	u8 master_driver_init_complete_valid;
+	u8 master_driver_init_complete;
+	u8 data_usage_quota_reached_valid;
+	u8 data_usage_quota_reached;
+};
+
+/* The response to a IPA_QMI_INDICATION_REGISTER request consists only of
+ * a standard QMI response.
+ */
+struct ipa_indication_register_rsp {
+	struct qmi_response_type_v01 rsp;
+};
+
+/* Request message for the IPA_QMI_DRIVER_INIT_COMPLETE request */
+struct ipa_driver_init_complete_req {
+	u8 status;
+};
+
+/* The response to a IPA_QMI_DRIVER_INIT_COMPLETE request consists only
+ * of a standard QMI response.
+ */
+struct ipa_driver_init_complete_rsp {
+	struct qmi_response_type_v01 rsp;
+};
+
+/* The message for the IPA_QMI_INIT_COMPLETE_IND indication consists
+ * only of a standard QMI response.
+ */
+struct ipa_init_complete_ind {
+	struct qmi_response_type_v01 status;
+};
+
+/* The AP tells the modem its platform type.  We assume Android. */
+enum ipa_platform_type {
+	IPA_QMI_PLATFORM_TYPE_INVALID		= 0,	/* Invalid */
+	IPA_QMI_PLATFORM_TYPE_TN		= 1,	/* Data card */
+	IPA_QMI_PLATFORM_TYPE_LE		= 2,	/* Data router */
+	IPA_QMI_PLATFORM_TYPE_MSM_ANDROID	= 3,	/* Android MSM */
+	IPA_QMI_PLATFORM_TYPE_MSM_WINDOWS	= 4,	/* Windows MSM */
+	IPA_QMI_PLATFORM_TYPE_MSM_QNX_V01	= 5,	/* QNX MSM */
+};
+
+/* This defines the start and end offset of a range of memory.  Both
+ * fields are offsets relative to the start of IPA shared memory.
+ * The end value is the last addressable byte *within* the range.
+ */
+struct ipa_mem_bounds {
+	u32 start;
+	u32 end;
+};
+
+/* This defines the location and size of an array.  The start value
+ * is an offset relative to the start of IPA shared memory.  The
+ * size of the array is implied by the number of entries (the entry
+ * size is assumed to be known).
+ */
+struct ipa_mem_array {
+	u32 start;
+	u32 count;
+};
+
+/* This defines the location and size of a range of memory.  The
+ * start is an offset relative to the start of IPA shared memory.
+ * This differs from the ipa_mem_bounds structure in that the size
+ * (in bytes) of the memory region is specified rather than the
+ * offset of its last byte.
+ */
+struct ipa_mem_range {
+	u32 start;
+	u32 size;
+};
+
+/* The message for the IPA_QMI_INIT_DRIVER request contains information
+ * from the AP that affects modem initialization.
+ */
+struct ipa_init_modem_driver_req {
+	u8			platform_type_valid;
+	u32			platform_type;	/* enum ipa_platform_type */
+
+	/* Modem header table information.  This defines the IPA shared
+	 * memory in which the modem may insert header table entries.
+	 */
+	u8			hdr_tbl_info_valid;
+	struct ipa_mem_bounds	hdr_tbl_info;
+
+	/* Routing table information.  These define the location and size of
+	 * non-hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_route_tbl_info_valid;
+	struct ipa_mem_array	v4_route_tbl_info;
+	u8			v6_route_tbl_info_valid;
+	struct ipa_mem_array	v6_route_tbl_info;
+
+	/* Filter table information.  These define the location and size of
+	 * non-hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_filter_tbl_start_valid;
+	u32			v4_filter_tbl_start;
+	u8			v6_filter_tbl_start_valid;
+	u32			v6_filter_tbl_start;
+
+	/* Modem memory information.  This defines the location and
+	 * size of memory available for the modem to use.
+	 */
+	u8			modem_mem_info_valid;
+	struct ipa_mem_range	modem_mem_info;
+
+	/* This defines the destination endpoint on the AP to which
+	 * the modem driver can send control commands.  IPA supports
+	 * 20 endpoints, so this must be 19 or less.
+	 */
+	u8			ctrl_comm_dest_end_pt_valid;
+	u32			ctrl_comm_dest_end_pt;
+
+	/* This defines whether the modem should load the microcontroller
+	 * or not.  It is unnecessary to reload it if the modem is being
+	 * restarted.
+	 *
+	 * NOTE: this field is named "is_ssr_bootup" elsewhere.
+	 */
+	u8			skip_uc_load_valid;
+	u8			skip_uc_load;
+
+	/* Processing context memory information.  This defines the memory in
+	 * which the modem may insert header processing context table entries.
+	 */
+	u8			hdr_proc_ctx_tbl_info_valid;
+	struct ipa_mem_bounds	hdr_proc_ctx_tbl_info;
+
+	/* Compression command memory information.  This defines the memory
+	 * in which the modem may insert compression/decompression commands.
+	 */
+	u8			zip_tbl_info_valid;
+	struct ipa_mem_bounds	zip_tbl_info;
+
+	/* Routing table information.  These define the location and size
+	 * of hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_hash_route_tbl_info_valid;
+	struct ipa_mem_array	v4_hash_route_tbl_info;
+	u8			v6_hash_route_tbl_info_valid;
+	struct ipa_mem_array	v6_hash_route_tbl_info;
+
+	/* Filter table information.  These define the location and size
+	 * of hashable IPv4 and IPv6 filter tables.  The start values are
+	 * offsets relative to the start of IPA shared memory.
+	 */
+	u8			v4_hash_filter_tbl_start_valid;
+	u32			v4_hash_filter_tbl_start;
+	u8			v6_hash_filter_tbl_start_valid;
+	u32			v6_hash_filter_tbl_start;
+};
+
+/* The response to a IPA_QMI_INIT_DRIVER request begins with a standard
+ * QMI response, but contains other information as well.  Currently we
+ * simply wait for the the INIT_DRIVER transaction to complete and
+ * ignore any other data that might be returned.
+ */
+struct ipa_init_modem_driver_rsp {
+	struct qmi_response_type_v01	rsp;
+
+	/* This defines the destination endpoint on the modem to which
+	 * the AP driver can send control commands.  IPA supports
+	 * 20 endpoints, so this must be 19 or less.
+	 */
+	u8				ctrl_comm_dest_end_pt_valid;
+	u32				ctrl_comm_dest_end_pt;
+
+	/* This defines the default endpoint.  The AP driver is not
+	 * required to configure the hardware with this value.  IPA
+	 * supports 20 endpoints, so this must be 19 or less.
+	 */
+	u8				default_end_pt_valid;
+	u32				default_end_pt;
+
+	/* This defines whether a second handshake is required to complete
+	 * initialization.
+	 */
+	u8				modem_driver_init_pending_valid;
+	u8				modem_driver_init_pending;
+};
+
+/* Message structure definitions defined in "ipa_qmi_msg.c" */
+extern struct qmi_elem_info ipa_indication_register_req_ei[];
+extern struct qmi_elem_info ipa_indication_register_rsp_ei[];
+extern struct qmi_elem_info ipa_driver_init_complete_req_ei[];
+extern struct qmi_elem_info ipa_driver_init_complete_rsp_ei[];
+extern struct qmi_elem_info ipa_init_complete_ind_ei[];
+extern struct qmi_elem_info ipa_mem_bounds_ei[];
+extern struct qmi_elem_info ipa_mem_array_ei[];
+extern struct qmi_elem_info ipa_mem_range_ei[];
+extern struct qmi_elem_info ipa_init_modem_driver_req_ei[];
+extern struct qmi_elem_info ipa_init_modem_driver_rsp_ei[];
+
+#endif /* !_IPA_QMI_MSG_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

(Much of the following is copied from text in "ipa_reg.c".  Please
see that for the more complete explanation.)

The IPA code abstracts the details of its 32-bit registers, allowing
access to them to be done generically.  The original motivation for
this was that the field width and/or position for values stored in
some registers differed for different versions of IPA hardware.
Abstracting access this way allows code that uses such registers to
be simpler, describing how register fields are used without
proliferating special-case code that is dependent on hardware
version.

Each IPA register has a name, which is one of the values in the
"ipa_reg" enumerated type (e.g., IPA_ENABLED_PIPES).  The offset
(memory address) of the register having a given name is maintained
internal to the "ipa_reg" module.

Some registers hold one or more fields that are less than 32 bits
wide.  Each of these registers has a data structure that breaks out
those fields into individual (32-bit) values.  These field structures
allow the register contents to be defined in a hardware independent
way.  Such registers have a pair of functions associated with them
to "construct" (when writing) and "parse" (when reading) the fields
found within them, using the register's fields structure.  This
allows the content of these registers to be read in a generic way.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_reg.c | 972 ++++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_reg.h | 614 ++++++++++++++++++++++++
 2 files changed, 1586 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_reg.c
 create mode 100644 drivers/net/ipa/ipa_reg.h

diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
new file mode 100644
index 000000000000..5e0aa6163235
--- /dev/null
+++ b/drivers/net/ipa/ipa_reg.c
@@ -0,0 +1,972 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/bitfield.h>
+
+#include "ipa_reg.h"
+
+/* I/O remapped base address of IPA register space */
+static void __iomem *ipa_reg_virt;
+
+/* struct ipa_reg_desc - descriptor for an abstracted hardware register
+ *
+ * @construct - fn to construct the register value from its field structure
+ * @parse - function to parse register field values into its field structure
+ * @offset - register offset relative to base address
+ * @n_ofst - size multiplier for "N-parameterized" registers
+ */
+struct ipa_reg_desc {
+	u32 (*construct)(enum ipa_reg reg, const void *fields);
+	void (*parse)(enum ipa_reg reg, void *fields, u32 val);
+	u32 offset;
+	u16 n_ofst;
+};
+
+/* IPA_ROUTE register */
+
+void ipa_reg_route(struct ipa_reg_route *route, u32 ep_id)
+{
+	route->route_dis = 0;
+	route->route_def_pipe = ep_id;
+	route->route_def_hdr_table = 1;
+	route->route_def_hdr_ofst = 0;
+	route->route_frag_def_pipe = ep_id;
+	route->route_def_retain_hdr = 1;
+}
+
+#define ROUTE_DIS_FMASK			0x00000001
+#define ROUTE_DEF_PIPE_FMASK		0x0000003e
+#define ROUTE_DEF_HDR_TABLE_FMASK	0x00000040
+#define ROUTE_DEF_HDR_OFST_FMASK	0x0001ff80
+#define ROUTE_FRAG_DEF_PIPE_FMASK	0x003e0000
+#define ROUTE_DEF_RETAIN_HDR_FMASK	0x01000000
+
+static u32 ipa_reg_construct_route(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_route *route = fields;
+	u32 val;
+
+	val = FIELD_PREP(ROUTE_DIS_FMASK, route->route_dis);
+	val |= FIELD_PREP(ROUTE_DEF_PIPE_FMASK, route->route_def_pipe);
+	val |= FIELD_PREP(ROUTE_DEF_HDR_TABLE_FMASK,
+			  route->route_def_hdr_table);
+	val |= FIELD_PREP(ROUTE_DEF_HDR_OFST_FMASK, route->route_def_hdr_ofst);
+	val |= FIELD_PREP(ROUTE_FRAG_DEF_PIPE_FMASK,
+			  route->route_frag_def_pipe);
+	val |= FIELD_PREP(ROUTE_DEF_RETAIN_HDR_FMASK,
+			  route->route_def_retain_hdr);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_HDR_N register */
+
+static void
+ipa_reg_endp_init_hdr_common(struct ipa_reg_endp_init_hdr *init_hdr)
+{
+	init_hdr->hdr_additional_const_len = 0;	/* XXX description? */
+	init_hdr->hdr_a5_mux = 0;		/* XXX description? */
+	init_hdr->hdr_len_inc_deagg_hdr = 0;	/* XXX description? */
+	init_hdr->hdr_metadata_reg_valid = 0;	/* XXX description? */
+}
+
+void ipa_reg_endp_init_hdr_cons(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset)
+{
+	init_hdr->hdr_len = header_size;
+	init_hdr->hdr_ofst_metadata_valid = 1;
+	init_hdr->hdr_ofst_metadata = metadata_offset;	/* XXX ignored */
+	init_hdr->hdr_ofst_pkt_size_valid = 1;
+	init_hdr->hdr_ofst_pkt_size = length_offset;
+
+	ipa_reg_endp_init_hdr_common(init_hdr);
+}
+
+void ipa_reg_endp_init_hdr_prod(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset)
+{
+	init_hdr->hdr_len = header_size;
+	init_hdr->hdr_ofst_metadata_valid = 1;
+	init_hdr->hdr_ofst_metadata = metadata_offset;
+	init_hdr->hdr_ofst_pkt_size_valid = 1;
+	init_hdr->hdr_ofst_pkt_size = length_offset;	/* XXX ignored */
+
+	ipa_reg_endp_init_hdr_common(init_hdr);
+}
+
+#define HDR_LEN_FMASK			0x0000003f
+#define HDR_OFST_METADATA_VALID_FMASK	0x00000040
+#define HDR_OFST_METADATA_FMASK		0x00001f80
+#define HDR_ADDITIONAL_CONST_LEN_FMASK	0x0007e000
+#define HDR_OFST_PKT_SIZE_VALID_FMASK	0x00080000
+#define HDR_OFST_PKT_SIZE_FMASK		0x03f00000
+#define HDR_A5_MUX_FMASK		0x04000000
+#define HDR_LEN_INC_DEAGG_HDR_FMASK	0x08000000
+#define HDR_METADATA_REG_VALID_FMASK	0x10000000
+
+static u32
+ipa_reg_construct_endp_init_hdr_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_hdr *init_hdr = fields;
+	u32 val;
+
+	val = FIELD_PREP(HDR_LEN_FMASK, init_hdr->hdr_len);
+	val |= FIELD_PREP(HDR_OFST_METADATA_VALID_FMASK,
+			  init_hdr->hdr_ofst_metadata_valid);
+	val |= FIELD_PREP(HDR_OFST_METADATA_FMASK, init_hdr->hdr_ofst_metadata);
+	val |= FIELD_PREP(HDR_ADDITIONAL_CONST_LEN_FMASK,
+			  init_hdr->hdr_additional_const_len);
+	val |= FIELD_PREP(HDR_OFST_PKT_SIZE_VALID_FMASK,
+			  init_hdr->hdr_ofst_pkt_size_valid);
+	val |= FIELD_PREP(HDR_OFST_PKT_SIZE_FMASK,
+			  init_hdr->hdr_ofst_pkt_size);
+	val |= FIELD_PREP(HDR_A5_MUX_FMASK, init_hdr->hdr_a5_mux);
+	val |= FIELD_PREP(HDR_LEN_INC_DEAGG_HDR_FMASK,
+			  init_hdr->hdr_len_inc_deagg_hdr);
+	val |= FIELD_PREP(HDR_METADATA_REG_VALID_FMASK,
+			  init_hdr->hdr_metadata_reg_valid);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_HDR_EXT_N register */
+
+void ipa_reg_endp_init_hdr_ext_common(struct ipa_reg_endp_init_hdr_ext *hdr_ext)
+{
+	hdr_ext->hdr_endianness = 1;			/* big endian */
+	hdr_ext->hdr_total_len_or_pad_valid = 1;
+	hdr_ext->hdr_total_len_or_pad = 0;		/* pad */
+	hdr_ext->hdr_total_len_or_pad_offset = 0;	/* XXX description? */
+}
+
+void ipa_reg_endp_init_hdr_ext_cons(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align, bool pad_included)
+{
+	hdr_ext->hdr_payload_len_inc_padding = pad_included ? 1 : 0;
+	hdr_ext->hdr_pad_to_alignment = pad_align;
+
+	ipa_reg_endp_init_hdr_ext_common(hdr_ext);
+}
+
+void ipa_reg_endp_init_hdr_ext_prod(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align)
+{
+	hdr_ext->hdr_payload_len_inc_padding = 0;
+	hdr_ext->hdr_pad_to_alignment = pad_align;	/* XXX ignored */
+
+	ipa_reg_endp_init_hdr_ext_common(hdr_ext);
+}
+
+#define HDR_ENDIANNESS_FMASK			0x00000001
+#define HDR_TOTAL_LEN_OR_PAD_VALID_FMASK	0x00000002
+#define HDR_TOTAL_LEN_OR_PAD_FMASK		0x00000004
+#define HDR_PAYLOAD_LEN_INC_PADDING_FMASK	0x00000008
+#define HDR_TOTAL_LEN_OR_PAD_OFFSET_FMASK	0x000003f0
+#define HDR_PAD_TO_ALIGNMENT_FMASK		0x00003c00
+
+static u32
+ipa_reg_construct_endp_init_hdr_ext_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_hdr_ext *init_hdr_ext = fields;
+	u32 val;
+
+	/* 0 = little endian; 1 = big endian */
+	val = FIELD_PREP(HDR_ENDIANNESS_FMASK, 1);
+	val |= FIELD_PREP(HDR_TOTAL_LEN_OR_PAD_VALID_FMASK,
+			  init_hdr_ext->hdr_total_len_or_pad_valid);
+	val |= FIELD_PREP(HDR_TOTAL_LEN_OR_PAD_FMASK,
+			  init_hdr_ext->hdr_total_len_or_pad);
+	val |= FIELD_PREP(HDR_PAYLOAD_LEN_INC_PADDING_FMASK,
+			  init_hdr_ext->hdr_payload_len_inc_padding);
+	val |= FIELD_PREP(HDR_TOTAL_LEN_OR_PAD_OFFSET_FMASK, 0);
+	val |= FIELD_PREP(HDR_PAD_TO_ALIGNMENT_FMASK,
+			  init_hdr_ext->hdr_pad_to_alignment);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_AGGR_N register */
+
+static void
+ipa_reg_endp_init_aggr_common(struct ipa_reg_endp_init_aggr *init_aggr)
+{
+	init_aggr->aggr_force_close = 0;	/* XXX description?  */
+	init_aggr->aggr_hard_byte_limit_en = 0;	/* XXX ignored for PROD? */
+}
+
+void ipa_reg_endp_init_aggr_cons(struct ipa_reg_endp_init_aggr *init_aggr,
+				 u32 byte_limit, u32 packet_limit,
+				 bool close_on_eof)
+{
+	init_aggr->aggr_en = IPA_ENABLE_AGGR;
+	init_aggr->aggr_type = IPA_GENERIC;
+	init_aggr->aggr_byte_limit = byte_limit;
+	init_aggr->aggr_time_limit = IPA_AGGR_TIME_LIMIT_DEFAULT;
+	init_aggr->aggr_pkt_limit = packet_limit;
+	init_aggr->aggr_sw_eof_active = close_on_eof ? 1 : 0;
+
+	ipa_reg_endp_init_aggr_common(init_aggr);
+}
+
+void ipa_reg_endp_init_aggr_prod(struct ipa_reg_endp_init_aggr *init_aggr,
+				 enum ipa_aggr_en aggr_en,
+				 enum ipa_aggr_type aggr_type)
+{
+	init_aggr->aggr_en = (u32)aggr_en;
+	init_aggr->aggr_type = aggr_en == IPA_BYPASS_AGGR ? 0 : (u32)aggr_type;
+	init_aggr->aggr_byte_limit = 0;		/* ignored */
+	init_aggr->aggr_time_limit = 0;		/* ignored */
+	init_aggr->aggr_pkt_limit = 0;		/* ignored */
+	init_aggr->aggr_sw_eof_active = 0;	/* ignored */
+
+	ipa_reg_endp_init_aggr_common(init_aggr);
+}
+
+#define AGGR_EN_FMASK				0x00000003
+#define AGGR_TYPE_FMASK				0x0000001c
+#define AGGR_BYTE_LIMIT_FMASK			0x000003e0
+#define AGGR_TIME_LIMIT_FMASK			0x00007c00
+#define AGGR_PKT_LIMIT_FMASK			0x001f8000
+#define AGGR_SW_EOF_ACTIVE_FMASK		0x00200000
+#define AGGR_FORCE_CLOSE_FMASK			0x00400000
+#define AGGR_HARD_BYTE_LIMIT_ENABLE_FMASK	0x01000000
+
+static u32
+ipa_reg_construct_endp_init_aggr_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_aggr *init_aggr = fields;
+	u32 val;
+
+	val = FIELD_PREP(AGGR_EN_FMASK, init_aggr->aggr_en);
+	val |= FIELD_PREP(AGGR_TYPE_FMASK, init_aggr->aggr_type);
+	val |= FIELD_PREP(AGGR_BYTE_LIMIT_FMASK, init_aggr->aggr_byte_limit);
+	val |= FIELD_PREP(AGGR_TIME_LIMIT_FMASK, init_aggr->aggr_time_limit);
+	val |= FIELD_PREP(AGGR_PKT_LIMIT_FMASK, init_aggr->aggr_pkt_limit);
+	val |= FIELD_PREP(AGGR_SW_EOF_ACTIVE_FMASK,
+			  init_aggr->aggr_sw_eof_active);
+	val |= FIELD_PREP(AGGR_FORCE_CLOSE_FMASK, init_aggr->aggr_force_close);
+	val |= FIELD_PREP(AGGR_HARD_BYTE_LIMIT_ENABLE_FMASK,
+			  init_aggr->aggr_hard_byte_limit_en);
+
+	return val;
+}
+
+static void
+ipa_reg_parse_endp_init_aggr_n(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_reg_endp_init_aggr *init_aggr = fields;
+
+	memset(init_aggr, 0, sizeof(*init_aggr));
+
+	init_aggr->aggr_en = FIELD_GET(AGGR_EN_FMASK, val);
+	init_aggr->aggr_type = FIELD_GET(AGGR_TYPE_FMASK, val);
+	init_aggr->aggr_byte_limit = FIELD_GET(AGGR_BYTE_LIMIT_FMASK, val);
+	init_aggr->aggr_time_limit = FIELD_GET(AGGR_TIME_LIMIT_FMASK, val);
+	init_aggr->aggr_pkt_limit = FIELD_GET(AGGR_PKT_LIMIT_FMASK, val);
+	init_aggr->aggr_sw_eof_active =
+			FIELD_GET(AGGR_SW_EOF_ACTIVE_FMASK, val);
+	init_aggr->aggr_force_close = FIELD_GET(AGGR_SW_EOF_ACTIVE_FMASK, val);
+	init_aggr->aggr_hard_byte_limit_en =
+			FIELD_GET(AGGR_HARD_BYTE_LIMIT_ENABLE_FMASK, val);
+}
+
+/* IPA_AGGR_FORCE_CLOSE register */
+
+void ipa_reg_aggr_force_close(struct ipa_reg_aggr_force_close *force_close,
+			      u32 pipe_bitmap)
+{
+	force_close->pipe_bitmap = pipe_bitmap;
+}
+
+#define PIPE_BITMAP_FMASK	0x000fffff
+
+static u32
+ipa_reg_construct_aggr_force_close(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_aggr_force_close *force_close = fields;
+
+	return FIELD_PREP(PIPE_BITMAP_FMASK, force_close->pipe_bitmap);
+}
+
+/* IPA_ENDP_INIT_MODE_N register */
+
+static void
+ipa_reg_endp_init_mode_common(struct ipa_reg_endp_init_mode *init_mode)
+{
+	init_mode->byte_threshold = 0;		/* XXX description? */
+	init_mode->pipe_replication_en = 0;	/* XXX description? */
+	init_mode->pad_en = 0;			/* XXX description? */
+	init_mode->hdr_ftch_disable = 0;	/* XXX description? */
+}
+
+/* IPA_ENDP_INIT_MODE is not valid for consumer pipes */
+void ipa_reg_endp_init_mode_cons(struct ipa_reg_endp_init_mode *init_mode)
+{
+	init_mode->mode = 0;            /* ignored */
+	init_mode->dest_pipe_index = 0; /* ignored */
+
+	ipa_reg_endp_init_mode_common(init_mode);
+}
+
+void ipa_reg_endp_init_mode_prod(struct ipa_reg_endp_init_mode *init_mode,
+				 enum ipa_mode mode, u32 dest_endp)
+{
+	init_mode->mode = mode;
+	init_mode->dest_pipe_index = mode == IPA_DMA ? dest_endp : 0;
+
+	ipa_reg_endp_init_mode_common(init_mode);
+}
+
+#define MODE_FMASK			0x00000007
+#define DEST_PIPE_INDEX_FMASK		0x000001f0
+#define BYTE_THRESHOLD_FMASK		0x0ffff000
+#define PIPE_REPLICATION_EN_FMASK	0x10000000
+#define PAD_EN_FMASK			0x20000000
+#define HDR_FTCH_DISABLE_FMASK		0x40000000
+
+static u32
+ipa_reg_construct_endp_init_mode_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_mode *init_mode = fields;
+	u32 val;
+
+	val = FIELD_PREP(MODE_FMASK, init_mode->mode);
+	val |= FIELD_PREP(DEST_PIPE_INDEX_FMASK, init_mode->dest_pipe_index);
+	val |= FIELD_PREP(BYTE_THRESHOLD_FMASK, init_mode->byte_threshold);
+	val |= FIELD_PREP(PIPE_REPLICATION_EN_FMASK,
+			  init_mode->pipe_replication_en);
+	val |= FIELD_PREP(PAD_EN_FMASK, init_mode->pad_en);
+	val |= FIELD_PREP(HDR_FTCH_DISABLE_FMASK, init_mode->hdr_ftch_disable);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_CTRL_N register */
+
+void
+ipa_reg_endp_init_ctrl(struct ipa_reg_endp_init_ctrl *init_ctrl, bool suspend)
+{
+	init_ctrl->endp_suspend = suspend ? 1 : 0;
+	init_ctrl->endp_delay = 0;
+}
+
+#define ENDP_SUSPEND_FMASK	0x00000001
+#define ENDP_DELAY_FMASK	0x00000002
+
+static u32
+ipa_reg_construct_endp_init_ctrl_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_ctrl *init_ctrl = fields;
+	u32 val;
+
+	val = FIELD_PREP(ENDP_SUSPEND_FMASK, init_ctrl->endp_suspend);
+	val |= FIELD_PREP(ENDP_DELAY_FMASK, init_ctrl->endp_delay);
+
+	return val;
+}
+
+static void
+ipa_reg_parse_endp_init_ctrl_n(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_reg_endp_init_ctrl *init_ctrl = fields;
+
+	memset(init_ctrl, 0, sizeof(*init_ctrl));
+
+	init_ctrl->endp_suspend = FIELD_GET(ENDP_SUSPEND_FMASK, val);
+	init_ctrl->endp_delay = FIELD_GET(ENDP_DELAY_FMASK, val);
+}
+
+/* IPA_ENDP_INIT_DEAGGR_N register */
+
+static void
+ipa_reg_endp_init_deaggr_common(struct ipa_reg_endp_init_deaggr *init_deaggr)
+{
+	init_deaggr->deaggr_hdr_len = 0;		/* XXX description? */
+	init_deaggr->packet_offset_valid = 0;		/* XXX description? */
+	init_deaggr->packet_offset_location = 0;	/* XXX description? */
+	init_deaggr->max_packet_len = 0;		/* XXX description? */
+}
+
+/* XXX The deaggr setting seems not to be valid for consumer endpoints */
+void
+ipa_reg_endp_init_deaggr_cons(struct ipa_reg_endp_init_deaggr *init_deaggr)
+{
+	ipa_reg_endp_init_deaggr_common(init_deaggr);
+}
+
+void
+ipa_reg_endp_init_deaggr_prod(struct ipa_reg_endp_init_deaggr *init_deaggr)
+{
+	ipa_reg_endp_init_deaggr_common(init_deaggr);
+}
+
+#define DEAGGR_HDR_LEN_FMASK		0x0000003f
+#define PACKET_OFFSET_VALID_FMASK	0x00000080
+#define PACKET_OFFSET_LOCATION_FMASK	0x00003f00
+#define MAX_PACKET_LEN_FMASK		0xffff0000
+
+static u32
+ipa_reg_construct_endp_init_deaggr_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_deaggr *init_deaggr = fields;
+	u32 val;
+
+	/* fields value is completely ignored (can be NULL) */
+	val = FIELD_PREP(DEAGGR_HDR_LEN_FMASK, init_deaggr->deaggr_hdr_len);
+	val |= FIELD_PREP(PACKET_OFFSET_VALID_FMASK,
+			  init_deaggr->packet_offset_valid);
+	val |= FIELD_PREP(PACKET_OFFSET_LOCATION_FMASK,
+			  init_deaggr->packet_offset_location);
+	val |= FIELD_PREP(MAX_PACKET_LEN_FMASK,
+			  init_deaggr->max_packet_len);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_SEQ_N register */
+
+static void
+ipa_reg_endp_init_seq_common(struct ipa_reg_endp_init_seq *init_seq)
+{
+	init_seq->dps_seq_type = 0;	/* XXX description? */
+	init_seq->hps_rep_seq_type = 0;	/* XXX description? */
+	init_seq->dps_rep_seq_type = 0;	/* XXX description? */
+}
+
+void ipa_reg_endp_init_seq_cons(struct ipa_reg_endp_init_seq *init_seq)
+{
+	init_seq->hps_seq_type = 0;	/* ignored */
+
+	ipa_reg_endp_init_seq_common(init_seq);
+}
+
+void ipa_reg_endp_init_seq_prod(struct ipa_reg_endp_init_seq *init_seq,
+				enum ipa_seq_type seq_type)
+{
+	init_seq->hps_seq_type = (u32)seq_type;
+
+	ipa_reg_endp_init_seq_common(init_seq);
+}
+
+#define HPS_SEQ_TYPE_FMASK	0x0000000f
+#define DPS_SEQ_TYPE_FMASK	0x000000f0
+#define HPS_REP_SEQ_TYPE_FMASK	0x00000f00
+#define DPS_REP_SEQ_TYPE_FMASK	0x0000f000
+
+static u32
+ipa_reg_construct_endp_init_seq_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_seq *init_seq = fields;
+	u32 val;
+
+	val = FIELD_PREP(HPS_SEQ_TYPE_FMASK, init_seq->hps_seq_type);
+	val |= FIELD_PREP(DPS_SEQ_TYPE_FMASK, init_seq->dps_seq_type);
+	val |= FIELD_PREP(HPS_REP_SEQ_TYPE_FMASK, init_seq->hps_rep_seq_type);
+	val |= FIELD_PREP(DPS_REP_SEQ_TYPE_FMASK, init_seq->dps_rep_seq_type);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_CFG_N register */
+
+static void
+ipa_reg_endp_init_cfg_common(struct ipa_reg_endp_init_cfg *init_cfg)
+{
+	init_cfg->frag_offload_en = 0;		/* XXX description?  */
+	init_cfg->cs_gen_qmb_master_sel = 0;	/* XXX description?  */
+}
+
+void ipa_reg_endp_init_cfg_cons(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type)
+{
+	init_cfg->cs_offload_en = offload_type;
+	init_cfg->cs_metadata_hdr_offset = 0;	/* ignored */
+
+	ipa_reg_endp_init_cfg_common(init_cfg);
+}
+
+void ipa_reg_endp_init_cfg_prod(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type,
+				u32 metadata_offset)
+{
+	init_cfg->cs_offload_en = offload_type;
+	init_cfg->cs_metadata_hdr_offset = metadata_offset;
+
+	ipa_reg_endp_init_cfg_common(init_cfg);
+}
+
+#define FRAG_OFFLOAD_EN_FMASK		0x00000001
+#define CS_OFFLOAD_EN_FMASK		0x00000006
+#define CS_METADATA_HDR_OFFSET_FMASK	0x00000078
+#define CS_GEN_QMB_MASTER_SEL_FMASK	0x00000100
+
+static u32
+ipa_reg_construct_endp_init_cfg_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_cfg *init_cfg = fields;
+	u32 val;
+
+	val = FIELD_PREP(FRAG_OFFLOAD_EN_FMASK, init_cfg->frag_offload_en);
+	val |= FIELD_PREP(CS_OFFLOAD_EN_FMASK, init_cfg->cs_offload_en);
+	val |= FIELD_PREP(CS_METADATA_HDR_OFFSET_FMASK,
+			  init_cfg->cs_metadata_hdr_offset);
+	val |= FIELD_PREP(CS_GEN_QMB_MASTER_SEL_FMASK,
+			  init_cfg->cs_gen_qmb_master_sel);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_HDR_METADATA_MASK_N register */
+
+void ipa_reg_endp_init_hdr_metadata_mask_cons(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask,
+		u32 mask)
+{
+	metadata_mask->metadata_mask = mask;
+}
+
+/* IPA_ENDP_INIT_HDR_METADATA_MASK is not valid for producer pipes */
+void ipa_reg_endp_init_hdr_metadata_mask_prod(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask)
+{
+	metadata_mask->metadata_mask = 0;	/* ignored */
+}
+
+
+#define METADATA_MASK_FMASK	0xffffffff
+
+static u32 ipa_reg_construct_endp_init_hdr_metadata_mask_n(enum ipa_reg reg,
+							  const void *fields)
+{
+	const struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask;
+
+	metadata_mask = fields;
+
+	return FIELD_PREP(METADATA_MASK_FMASK, metadata_mask->metadata_mask);
+}
+
+/* IPA_SHARED_MEM_SIZE register (read-only) */
+
+#define SHARED_MEM_SIZE_FMASK	0x0000ffff
+#define SHARED_MEM_BADDR_FMASK	0xffff0000
+
+static void
+ipa_reg_parse_shared_mem_size(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_reg_shared_mem_size *mem_size = fields;
+
+	memset(mem_size, 0, sizeof(*mem_size));
+
+	mem_size->shared_mem_size = FIELD_GET(SHARED_MEM_SIZE_FMASK, val);
+	mem_size->shared_mem_baddr = FIELD_GET(SHARED_MEM_BADDR_FMASK, val);
+}
+
+/* IPA_ENDP_STATUS_N register */
+
+static void ipa_reg_endp_status_common(struct ipa_reg_endp_status *endp_status)
+{
+	endp_status->status_pkt_suppress = 0;	/* XXX description?  */
+}
+
+void ipa_reg_endp_status_cons(struct ipa_reg_endp_status *endp_status,
+			      bool enable)
+{
+	endp_status->status_en = enable ? 1 : 0;
+	endp_status->status_endp = 0;		/* ignored */
+	endp_status->status_location = 0;	/* before packet data */
+
+	ipa_reg_endp_status_common(endp_status);
+}
+
+void ipa_reg_endp_status_prod(struct ipa_reg_endp_status *endp_status,
+			      bool enable, u32 endp)
+{
+	endp_status->status_en = enable ? 1 : 0;
+	endp_status->status_endp = endp;
+	endp_status->status_location = 0;	/* ignored */
+
+	ipa_reg_endp_status_common(endp_status);
+}
+
+#define STATUS_EN_FMASK			0x00000001
+#define STATUS_ENDP_FMASK		0x0000003e
+#define STATUS_LOCATION_FMASK		0x00000100
+#define STATUS_PKT_SUPPRESS_FMASK	0x00000200
+
+static u32 ipa_reg_construct_endp_status_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_status *endp_status = fields;
+	u32 val;
+
+	val = FIELD_PREP(STATUS_EN_FMASK, endp_status->status_en);
+	val |= FIELD_PREP(STATUS_ENDP_FMASK, endp_status->status_endp);
+	val |= FIELD_PREP(STATUS_LOCATION_FMASK, endp_status->status_location);
+	val |= FIELD_PREP(STATUS_PKT_SUPPRESS_FMASK, 0);
+
+	return val;
+}
+
+/* IPA_ENDP_FILTER_ROUTER_HSH_CFG_N register */
+
+void ipa_reg_hash_tuple(struct ipa_reg_hash_tuple *tuple)
+{
+	tuple->src_id = 0;	/* pipe number in flt, table index in rt */
+	tuple->src_ip = 0;
+	tuple->dst_ip = 0;
+	tuple->src_port = 0;
+	tuple->dst_port = 0;
+	tuple->protocol = 0;
+	tuple->metadata = 0;
+	tuple->undefined = 0;
+}
+
+#define FILTER_HASH_MSK_SRC_ID_FMASK	0x00000001
+#define FILTER_HASH_MSK_SRC_IP_FMASK	0x00000002
+#define FILTER_HASH_MSK_DST_IP_FMASK	0x00000004
+#define FILTER_HASH_MSK_SRC_PORT_FMASK	0x00000008
+#define FILTER_HASH_MSK_DST_PORT_FMASK	0x00000010
+#define FILTER_HASH_MSK_PROTOCOL_FMASK	0x00000020
+#define FILTER_HASH_MSK_METADATA_FMASK	0x00000040
+#define FILTER_HASH_UNDEFINED1_FMASK	0x0000ff80
+
+#define ROUTER_HASH_MSK_SRC_ID_FMASK	0x00010000
+#define ROUTER_HASH_MSK_SRC_IP_FMASK	0x00020000
+#define ROUTER_HASH_MSK_DST_IP_FMASK	0x00040000
+#define ROUTER_HASH_MSK_SRC_PORT_FMASK	0x00080000
+#define ROUTER_HASH_MSK_DST_PORT_FMASK	0x00100000
+#define ROUTER_HASH_MSK_PROTOCOL_FMASK	0x00200000
+#define ROUTER_HASH_MSK_METADATA_FMASK	0x00400000
+#define ROUTER_HASH_UNDEFINED2_FMASK	0xff800000
+
+static u32 ipa_reg_construct_hash_cfg_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_ep_filter_router_hsh_cfg *hsh_cfg = fields;
+	u32 val;
+
+	val = FIELD_PREP(FILTER_HASH_MSK_SRC_ID_FMASK, hsh_cfg->flt.src_id);
+	val |= FIELD_PREP(FILTER_HASH_MSK_SRC_IP_FMASK, hsh_cfg->flt.src_ip);
+	val |= FIELD_PREP(FILTER_HASH_MSK_DST_IP_FMASK, hsh_cfg->flt.dst_ip);
+	val |= FIELD_PREP(FILTER_HASH_MSK_SRC_PORT_FMASK,
+			  hsh_cfg->flt.src_port);
+	val |= FIELD_PREP(FILTER_HASH_MSK_DST_PORT_FMASK,
+			  hsh_cfg->flt.dst_port);
+	val |= FIELD_PREP(FILTER_HASH_MSK_PROTOCOL_FMASK,
+			  hsh_cfg->flt.protocol);
+	val |= FIELD_PREP(FILTER_HASH_MSK_METADATA_FMASK,
+			  hsh_cfg->flt.metadata);
+	val |= FIELD_PREP(FILTER_HASH_UNDEFINED1_FMASK, hsh_cfg->flt.undefined);
+
+	val |= FIELD_PREP(ROUTER_HASH_MSK_SRC_ID_FMASK, hsh_cfg->rt.src_id);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_SRC_IP_FMASK, hsh_cfg->rt.src_ip);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_DST_IP_FMASK, hsh_cfg->rt.dst_ip);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_SRC_PORT_FMASK, hsh_cfg->rt.src_port);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_DST_PORT_FMASK, hsh_cfg->rt.dst_port);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_PROTOCOL_FMASK, hsh_cfg->rt.protocol);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_METADATA_FMASK, hsh_cfg->rt.metadata);
+	val |= FIELD_PREP(FILTER_HASH_UNDEFINED1_FMASK, hsh_cfg->rt.undefined);
+
+	return val;
+}
+
+static void ipa_reg_parse_hash_cfg_n(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_ep_filter_router_hsh_cfg *hsh_cfg = fields;
+
+	memset(hsh_cfg, 0, sizeof(*hsh_cfg));
+
+	hsh_cfg->flt.src_id = FIELD_GET(FILTER_HASH_MSK_SRC_ID_FMASK, val);
+	hsh_cfg->flt.src_ip = FIELD_GET(FILTER_HASH_MSK_SRC_IP_FMASK, val);
+	hsh_cfg->flt.dst_ip = FIELD_GET(FILTER_HASH_MSK_DST_IP_FMASK, val);
+	hsh_cfg->flt.src_port = FIELD_GET(FILTER_HASH_MSK_SRC_PORT_FMASK, val);
+	hsh_cfg->flt.dst_port = FIELD_GET(FILTER_HASH_MSK_DST_PORT_FMASK, val);
+	hsh_cfg->flt.protocol = FIELD_GET(FILTER_HASH_MSK_PROTOCOL_FMASK, val);
+	hsh_cfg->flt.metadata = FIELD_GET(FILTER_HASH_MSK_METADATA_FMASK, val);
+	hsh_cfg->flt.undefined = FIELD_GET(FILTER_HASH_UNDEFINED1_FMASK, val);
+
+	hsh_cfg->rt.src_id = FIELD_GET(ROUTER_HASH_MSK_SRC_ID_FMASK, val);
+	hsh_cfg->rt.src_ip = FIELD_GET(ROUTER_HASH_MSK_SRC_IP_FMASK, val);
+	hsh_cfg->rt.dst_ip = FIELD_GET(ROUTER_HASH_MSK_DST_IP_FMASK, val);
+	hsh_cfg->rt.src_port = FIELD_GET(ROUTER_HASH_MSK_SRC_PORT_FMASK, val);
+	hsh_cfg->rt.dst_port = FIELD_GET(ROUTER_HASH_MSK_DST_PORT_FMASK, val);
+	hsh_cfg->rt.protocol = FIELD_GET(ROUTER_HASH_MSK_PROTOCOL_FMASK, val);
+	hsh_cfg->rt.metadata = FIELD_GET(ROUTER_HASH_MSK_METADATA_FMASK, val);
+	hsh_cfg->rt.undefined = FIELD_GET(ROUTER_HASH_UNDEFINED2_FMASK, val);
+}
+
+/* IPA_RSRC_GRP_XY_RSRC_TYPE_N register(s) */
+
+void
+ipa_reg_rsrc_grp_xy_rsrc_type_n(struct ipa_reg_rsrc_grp_xy_rsrc_type_n *limits,
+				 u32 x_min, u32 x_max, u32 y_min, u32 y_max)
+{
+	limits->x_min = x_min;
+	limits->x_max = x_max;
+	limits->y_min = y_min;
+	limits->y_max = y_max;
+}
+
+#define X_MIN_LIM_FMASK	0x0000003f
+#define X_MAX_LIM_FMASK	0x00003f00
+#define Y_MIN_LIM_FMASK	0x003f0000
+#define Y_MAX_LIM_FMASK	0x3f000000
+
+static u32
+ipa_reg_construct_rsrg_grp_xy_rsrc_type_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_rsrc_grp_xy_rsrc_type_n *limits = fields;
+	u32 val;
+
+	val = FIELD_PREP(X_MIN_LIM_FMASK, limits->x_min);
+	val |= FIELD_PREP(X_MAX_LIM_FMASK, limits->x_max);
+
+	/* DST_23 register has only X fields at ipa V3_5 */
+	if (reg == IPA_DST_RSRC_GRP_23_RSRC_TYPE_N)
+		return val;
+
+	val |= FIELD_PREP(Y_MIN_LIM_FMASK, limits->y_min);
+	val |= FIELD_PREP(Y_MAX_LIM_FMASK, limits->y_max);
+
+	return val;
+}
+
+/* IPA_QSB_MAX_WRITES register */
+
+void ipa_reg_qsb_max_writes(struct ipa_reg_qsb_max_writes *max_writes,
+			    u32 qmb_0_max_writes, u32 qmb_1_max_writes)
+{
+	max_writes->qmb_0_max_writes = qmb_0_max_writes;
+	max_writes->qmb_1_max_writes = qmb_1_max_writes;
+}
+
+#define GEN_QMB_0_MAX_WRITES_FMASK	0x0000000f
+#define GEN_QMB_1_MAX_WRITES_FMASK	0x000000f0
+
+static u32
+ipa_reg_construct_qsb_max_writes(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_qsb_max_writes *max_writes = fields;
+	u32 val;
+
+	val = FIELD_PREP(GEN_QMB_0_MAX_WRITES_FMASK,
+			  max_writes->qmb_0_max_writes);
+	val |= FIELD_PREP(GEN_QMB_1_MAX_WRITES_FMASK,
+			  max_writes->qmb_1_max_writes);
+
+	return val;
+}
+
+/* IPA_QSB_MAX_READS register */
+
+void ipa_reg_qsb_max_reads(struct ipa_reg_qsb_max_reads *max_reads,
+			   u32 qmb_0_max_reads, u32 qmb_1_max_reads)
+{
+	max_reads->qmb_0_max_reads = qmb_0_max_reads;
+	max_reads->qmb_1_max_reads = qmb_1_max_reads;
+}
+
+#define GEN_QMB_0_MAX_READS_FMASK	0x0000000f
+#define GEN_QMB_1_MAX_READS_FMASK	0x000000f0
+
+static u32 ipa_reg_construct_qsb_max_reads(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_qsb_max_reads *max_reads = fields;
+	u32 val;
+
+	val = FIELD_PREP(GEN_QMB_0_MAX_READS_FMASK, max_reads->qmb_0_max_reads);
+	val |= FIELD_PREP(GEN_QMB_1_MAX_READS_FMASK,
+			  max_reads->qmb_1_max_reads);
+
+	return val;
+}
+
+/* IPA_IDLE_INDICATION_CFG register */
+
+void ipa_reg_idle_indication_cfg(struct ipa_reg_idle_indication_cfg *indication,
+				 u32 debounce_thresh, bool non_idle_enable)
+{
+	indication->enter_idle_debounce_thresh = debounce_thresh;
+	indication->const_non_idle_enable = non_idle_enable;
+}
+
+#define ENTER_IDLE_DEBOUNCE_THRESH_FMASK	0x0000ffff
+#define CONST_NON_IDLE_ENABLE_FMASK		0x00010000
+
+static u32
+ipa_reg_construct_idle_indication_cfg(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_idle_indication_cfg *indication_cfg;
+	u32 val;
+
+	indication_cfg = fields;
+
+	val = FIELD_PREP(ENTER_IDLE_DEBOUNCE_THRESH_FMASK,
+			  indication_cfg->enter_idle_debounce_thresh);
+	val |= FIELD_PREP(CONST_NON_IDLE_ENABLE_FMASK,
+			  indication_cfg->const_non_idle_enable);
+
+	return val;
+}
+
+/* The entries in the following table have the following constraints:
+ * - 0 is not a valid offset (it represents an unused entry).  It is
+ *   a bug for code to attempt to access a register which has an
+ *   undefined (zero) offset value.
+ * - If a construct function is supplied, the register must be
+ *   written using ipa_write_reg_n_fields() (or its wrapper
+ *   function ipa_write_reg_fields()).
+ * - Generally, if a parse function is supplied, the register should
+ *   read using ipa_read_reg_n_fields() (or ipa_read_reg_fields()).
+ *   (Currently some debug code reads some registers directly, without
+ *   parsing.)
+ */
+#define cfunc(f)	ipa_reg_construct_ ## f
+#define pfunc(f)	ipa_reg_parse_ ## f
+#define reg_obj_common(id, cf, pf, o, n)	\
+	[id] = {				\
+		.construct = cf,		\
+		.parse = pf,			\
+		.offset = o,			\
+		.n_ofst = n,			\
+	}
+#define reg_obj_cfunc(id, f, o, n)		\
+	reg_obj_common(id, cfunc(f), NULL, o, n)
+#define reg_obj_pfunc(id, f, o, n)		\
+	reg_obj_common(id, NULL, pfunc(f), o, n)
+#define reg_obj_both(id, f, o, n)		\
+	reg_obj_common(id, cfunc(f), pfunc(f), o, n)
+#define reg_obj_nofunc(id, o, n)		\
+	reg_obj_common(id, NULL, NULL, o, n)
+
+/* IPAv3.5.1 */
+static const struct ipa_reg_desc ipa_reg[] = {
+	reg_obj_cfunc(IPA_ROUTE, route,			0x00000048,	0x0000),
+	reg_obj_nofunc(IPA_IRQ_STTS_EE_N,		0x00003008,	0x1000),
+	reg_obj_nofunc(IPA_IRQ_EN_EE_N,			0x0000300c,	0x1000),
+	reg_obj_nofunc(IPA_IRQ_CLR_EE_N,		0x00003010,	0x1000),
+	reg_obj_nofunc(IPA_IRQ_SUSPEND_INFO_EE_N,	0x00003030,	0x1000),
+	reg_obj_nofunc(IPA_SUSPEND_IRQ_EN_EE_N,		0x00003034,	0x1000),
+	reg_obj_nofunc(IPA_SUSPEND_IRQ_CLR_EE_N,	0x00003038,	0x1000),
+	reg_obj_nofunc(IPA_BCR,				0x000001d0,	0x0000),
+	reg_obj_nofunc(IPA_ENABLED_PIPES,		0x00000038,	0x0000),
+	reg_obj_nofunc(IPA_TAG_TIMER,			0x00000060,	0x0000),
+	reg_obj_nofunc(IPA_STATE_AGGR_ACTIVE,		0x0000010c,	0x0000),
+	reg_obj_cfunc(IPA_ENDP_INIT_HDR_N,
+		      endp_init_hdr_n,			0x00000810,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_HDR_EXT_N,
+		      endp_init_hdr_ext_n,		0x00000814,	0x0070),
+	reg_obj_both(IPA_ENDP_INIT_AGGR_N,
+		     endp_init_aggr_n,			0x00000824,	0x0070),
+	reg_obj_cfunc(IPA_AGGR_FORCE_CLOSE,
+		     aggr_force_close,			0x000001ec,	0x0000),
+	reg_obj_cfunc(IPA_ENDP_INIT_MODE_N,
+		      endp_init_mode_n,			0x00000820,	0x0070),
+	reg_obj_both(IPA_ENDP_INIT_CTRL_N,
+		     endp_init_ctrl_n,			0x00000800,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_DEAGGR_N,
+		      endp_init_deaggr_n,		0x00000834,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_SEQ_N,
+		      endp_init_seq_n,			0x0000083c,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_CFG_N,
+		      endp_init_cfg_n,			0x00000808,	0x0070),
+	reg_obj_nofunc(IPA_IRQ_EE_UC_N,			0x0000301c,	0x1000),
+	reg_obj_cfunc(IPA_ENDP_INIT_HDR_METADATA_MASK_N,
+		      endp_init_hdr_metadata_mask_n,	0x00000818,	0x0070),
+	reg_obj_pfunc(IPA_SHARED_MEM_SIZE,
+		      shared_mem_size,			0x00000054,	0x0000),
+	reg_obj_nofunc(IPA_SRAM_DIRECT_ACCESS_N,	0x00007000,	0x0004),
+	reg_obj_nofunc(IPA_LOCAL_PKT_PROC_CNTXT_BASE,	0x000001e8,	0x0000),
+	reg_obj_cfunc(IPA_ENDP_STATUS_N,
+		      endp_status_n,			0x00000840,	0x0070),
+	reg_obj_both(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N,
+		     hash_cfg_n,			0x0000085c,	0x0070),
+	reg_obj_cfunc(IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000400,	0x0020),
+	reg_obj_cfunc(IPA_SRC_RSRC_GRP_23_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000404,	0x0020),
+	reg_obj_cfunc(IPA_DST_RSRC_GRP_01_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000500,	0x0020),
+	reg_obj_cfunc(IPA_DST_RSRC_GRP_23_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000504,	0x0020),
+	reg_obj_cfunc(IPA_QSB_MAX_WRITES,
+		      qsb_max_writes,			0x00000074,	0x0000),
+	reg_obj_cfunc(IPA_QSB_MAX_READS,
+		      qsb_max_reads,			0x00000078,	0x0000),
+	reg_obj_cfunc(IPA_IDLE_INDICATION_CFG,
+		      idle_indication_cfg,		0x00000220,	0x0000),
+};
+
+#undef reg_obj_nofunc
+#undef reg_obj_both
+#undef reg_obj_pfunc
+#undef reg_obj_cfunc
+#undef reg_obj_common
+#undef pfunc
+#undef cfunc
+
+int ipa_reg_init(phys_addr_t phys_addr, size_t size)
+{
+	ipa_reg_virt = ioremap(phys_addr, size);
+
+	return ipa_reg_virt ? 0 : -ENOMEM;
+}
+
+void ipa_reg_exit(void)
+{
+	iounmap(ipa_reg_virt);
+	ipa_reg_virt = NULL;
+}
+
+/* Get the offset of an "n parameterized" register */
+u32 ipa_reg_n_offset(enum ipa_reg reg, u32 n)
+{
+	return ipa_reg[reg].offset + n * ipa_reg[reg].n_ofst;
+}
+
+/* ipa_read_reg_n() - Get an "n parameterized" register's value */
+u32 ipa_read_reg_n(enum ipa_reg reg, u32 n)
+{
+	return ioread32(ipa_reg_virt + ipa_reg_n_offset(reg, n));
+}
+
+/* ipa_write_reg_n() - Write a raw value to an "n parameterized" register */
+void ipa_write_reg_n(enum ipa_reg reg, u32 n, u32 val)
+{
+	iowrite32(val, ipa_reg_virt + ipa_reg_n_offset(reg, n));
+}
+
+/* ipa_read_reg_n_fields() - Parse value of an "n parameterized" register */
+void ipa_read_reg_n_fields(enum ipa_reg reg, u32 n, void *fields)
+{
+	u32 val = ipa_read_reg_n(reg, n);
+
+	ipa_reg[reg].parse(reg, fields, val);
+}
+
+/* ipa_write_reg_n_fields() - Construct a vlaue to write to an "n
+ * parameterized" register
+ */
+void ipa_write_reg_n_fields(enum ipa_reg reg, u32 n, const void *fields)
+{
+	u32 val = ipa_reg[reg].construct(reg, fields);
+
+	ipa_write_reg_n(reg, n, val);
+}
+
+/* Maximum representable aggregation byte limit value (in bytes) */
+u32 ipa_reg_aggr_max_byte_limit(void)
+{
+	return FIELD_MAX(AGGR_BYTE_LIMIT_FMASK) * SZ_1K;
+}
+
+/* Maximum representable aggregation packet limit value */
+u32 ipa_reg_aggr_max_packet_limit(void)
+{
+	return FIELD_MAX(AGGR_PKT_LIMIT_FMASK);
+}
diff --git a/drivers/net/ipa/ipa_reg.h b/drivers/net/ipa/ipa_reg.h
new file mode 100644
index 000000000000..fb7c1ab6408c
--- /dev/null
+++ b/drivers/net/ipa/ipa_reg.h
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_REG_H_
+#define _IPA_REG_H_
+
+/**
+ * DOC: The IPA Register Abstraction
+ *
+ * The IPA code abstracts the details of its 32-bit registers, allowing access
+ * to them to be done generically.  The original motivation for this was that
+ * the field width and/or position for values stored in some registers differed
+ * for different versions of IPA hardware.  Abstracting access this way allows
+ * code that uses such registers to be simpler, describing how register fields
+ * are used without proliferating special-case code that is dependent on
+ * hardware version.
+ *
+ * Each IPA register has a name, which is one of the values in the "ipa_reg"
+ * enumerated type (e.g., IPA_ENABLED_PIPES).  The offset (memory address) of
+ * the register having a given name is maintained internal to the "ipa_reg"
+ * module.
+ *
+ * For simple registers that hold a single 32-bit value, two functions provide
+ * access to the register:
+ *	u32 ipa_read_reg(enum ipa_reg reg);
+ *	void ipa_write_reg(enum ipa_reg reg, u32 val);
+ *
+ * Some registers are "N-parameterized."  This means there is a set of
+ * registers having identical format, and each is accessed by supplying
+ * the "N" value to select which register is intended.  The names for
+ * N-parameterized registers have an "_N" suffix (e.g. IPA_IRQ_STTS_EE_N).
+ * Details of computing the offset for such registers are maintained internal
+ * to the "ipa_reg" module.  For simple registers holding a single 32-bit
+ * value, these functions provide access to N-parameterized registers:
+ *	u32 ipa_read_reg_n(enum ipa_reg reg, u32 n);
+ *	void ipa_write_reg_n(enum ipa_reg reg, u32 n, u32 val);
+ *
+ * Some registers contain fields less than 32 bits wide (call these "field
+ * registers").  For each such register a "field structure" is defined to
+ * represent the values of the individual fields within the register.  The
+ * name of the structure matches the name of the register (in lower case).
+ * For example, the individual fields in the IPA_ROUTE register are represented
+ * by the field structure named ipa_reg_route.
+ *
+ * Each field register has a function used to fill in its corresponding
+ * field structure with particular values.  Parameters to this function
+ * supply values to assign.  In many cases only a few such parameters
+ * are required, because some field values are invariant.  The name of
+ * this sort of function is derived from the structure name, so for example
+ * ipa_reg_route() is used to initialize an ipa_reg_route structure.
+ * Field registers associated with endpoints often use different fields
+ * or different values dependent on whether the endpoint is a producer or
+ * consumer.  In these cases separate functions are used to initialize
+ * the field structure (for example ipa_reg_endp_init_hdr_cons() and
+ * ipa_reg_endp_init_hdr_prod()).
+ *
+ * The position and width of fields within a register are defined (in
+ * "ipa_reg.c") using field masks, and the names of the members in the field
+ * structure associated with such registers match the names of the bit masks
+ * that define the fields.  (E.g., ipa_reg_route->route_dis is used to
+ * represent the field defined by the ROUTE_DIS field mask.)
+ *
+ * "Field registers" are accessed using these functions:
+ *	void ipa_read_reg_fields(enum ipa_reg reg, void *fields);
+ *	void ipa_write_reg_fields(enum ipa_reg reg, const void *fields);
+ * The "fields" parameter in both cases is the address of the "field structure"
+ * associated with the register being accessed.  When reading, the structure
+ * is filled by ipa_read_reg_fields() with values found in the register's
+ * fields.  (All fields will be filled; there is no need for the caller to
+ * initialize the passed-in structure before the call.)  When writing, the
+ * caller initializes the structure with all values that should be written to
+ * the fields in the register.
+ *
+ * "Field registers" can also be N-parameterized, in which case they are
+ * accessed using these functions:
+ *	void ipa_read_reg_n_fields(enum ipa_reg reg, u32 n, void *fields);
+ *	void ipa_write_reg_n_fields(enum ipa_reg reg, u32 n,
+ *				    const void *fields);
+ */
+
+/* Register names */
+enum ipa_reg {
+	IPA_ROUTE,
+	IPA_IRQ_STTS_EE_N,
+	IPA_IRQ_EN_EE_N,
+	IPA_IRQ_CLR_EE_N,
+	IPA_IRQ_SUSPEND_INFO_EE_N,
+	IPA_SUSPEND_IRQ_EN_EE_N,
+	IPA_SUSPEND_IRQ_CLR_EE_N,
+	IPA_BCR,
+	IPA_ENABLED_PIPES,
+	IPA_TAG_TIMER,
+	IPA_STATE_AGGR_ACTIVE,
+	IPA_ENDP_INIT_HDR_N,
+	IPA_ENDP_INIT_HDR_EXT_N,
+	IPA_ENDP_INIT_AGGR_N,
+	IPA_AGGR_FORCE_CLOSE,
+	IPA_ENDP_INIT_MODE_N,
+	IPA_ENDP_INIT_CTRL_N,
+	IPA_ENDP_INIT_DEAGGR_N,
+	IPA_ENDP_INIT_SEQ_N,
+	IPA_ENDP_INIT_CFG_N,
+	IPA_IRQ_EE_UC_N,
+	IPA_ENDP_INIT_HDR_METADATA_MASK_N,
+	IPA_SHARED_MEM_SIZE,
+	IPA_SRAM_DIRECT_ACCESS_N,
+	IPA_LOCAL_PKT_PROC_CNTXT_BASE,
+	IPA_ENDP_STATUS_N,
+	IPA_ENDP_FILTER_ROUTER_HSH_CFG_N,
+	IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N,
+	IPA_SRC_RSRC_GRP_23_RSRC_TYPE_N,
+	IPA_DST_RSRC_GRP_01_RSRC_TYPE_N,
+	IPA_DST_RSRC_GRP_23_RSRC_TYPE_N,
+	IPA_QSB_MAX_WRITES,
+	IPA_QSB_MAX_READS,
+	IPA_IDLE_INDICATION_CFG,
+};
+
+/**
+ * struct ipa_reg_route - IPA_ROUTE field structure
+ * @route_dis: route disable
+ * @route_def_pipe: route default pipe
+ * @route_def_hdr_table: route default header table
+ * @route_def_hdr_ofst: route default header offset table
+ * @route_frag_def_pipe: Default pipe to route fragmented exception
+ *    packets and frag new rule statues, if source pipe does not have
+ *    a notification status pipe defined.
+ * @route_def_retain_hdr: default value of retain header. It is used
+ *    when no rule was hit
+ */
+struct ipa_reg_route {
+	u32 route_dis;
+	u32 route_def_pipe;
+	u32 route_def_hdr_table;
+	u32 route_def_hdr_ofst;
+	u32 route_frag_def_pipe;
+	u32 route_def_retain_hdr;
+};
+
+/**
+ * ipa_reg_endp_init_hdr - ENDP_INIT_HDR_N field structure
+ *
+ * @hdr_len:
+ * @hdr_ofst_metadata_valid:
+ * @hdr_ofst_metadata:
+ * @hdr_additional_const_len:
+ * @hdr_ofst_pkt_size_valid:
+ * @hdr_ofst_pkt_size:
+ * @hdr_a5_mux:
+ * @hdr_len_inc_deagg_hdr:
+ * @hdr_metadata_reg_valid:
+*/
+struct ipa_reg_endp_init_hdr {
+	u32 hdr_len;
+	u32 hdr_ofst_metadata_valid;
+	u32 hdr_ofst_metadata;
+	u32 hdr_additional_const_len;
+	u32 hdr_ofst_pkt_size_valid;
+	u32 hdr_ofst_pkt_size;
+	u32 hdr_a5_mux;
+	u32 hdr_len_inc_deagg_hdr;
+	u32 hdr_metadata_reg_valid;
+};
+
+/**
+ * ipa_reg_endp_init_hdr_ext - IPA_ENDP_INIT_HDR_EXT_N field structure
+ *
+ * @hdr_endianness:
+ * @hdr_total_len_or_pad_valid:
+ * @hdr_total_len_or_pad:
+ * @hdr_payload_len_inc_padding:
+ * @hdr_total_len_or_pad_offset:
+ * @hdr_pad_to_alignment:
+ */
+struct ipa_reg_endp_init_hdr_ext {
+	u32 hdr_endianness;		/* 0 = little endian; 1 = big endian */
+	u32 hdr_total_len_or_pad_valid;
+	u32 hdr_total_len_or_pad;	/* 0 = pad; 1 = total_len */
+	u32 hdr_payload_len_inc_padding;
+	u32 hdr_total_len_or_pad_offset;
+	u32 hdr_pad_to_alignment;
+};
+
+/**
+ * enum ipa_aggr_en - aggregation setting type in IPA end-point
+ */
+enum ipa_aggr_en {
+	IPA_BYPASS_AGGR		= 0,
+	IPA_ENABLE_AGGR		= 1,
+	IPA_ENABLE_DEAGGR	= 2,
+};
+
+/**
+ * enum ipa_aggr_type - type of aggregation in IPA end-point
+ */
+enum ipa_aggr_type {
+	IPA_MBIM_16 = 0,
+	IPA_HDLC    = 1,
+	IPA_TLP	    = 2,
+	IPA_RNDIS   = 3,
+	IPA_GENERIC = 4,
+	IPA_QCMAP   = 6,
+};
+
+#define IPA_AGGR_TIME_LIMIT_DEFAULT	1	/* XXX units? */
+
+/**
+ * struct ipa_reg_endp_init_aggr - IPA_ENDP_INIT_AGGR_N field structure
+ * @aggr_en: bypass aggregation, enable aggregation, or deaggregation
+ *	     (enum ipa_aggr_en)
+ * @aggr_type: type of aggregation (enum ipa_aggr_type aggr)
+ * @aggr_byte_limit: aggregated byte limit in KB, or no limit if 0
+ *		     (producer pipes only)
+ * @aggr_time_limit: time limit before close of aggregation, or
+ *		     aggregation disabled if 0 (producer pipes only)
+ * @aggr_pkt_limit: packet limit before closing aggregation, or no
+ *		    limit if 0 (producer pipes only) XXX units
+ * @aggr_sw_eof_active: whether EOF closes aggregation--in addition to
+ *			hardware aggregation configuration (producer
+ *			pipes configured for generic aggregation only)
+ * @aggr_force_close: whether to force a close XXX verify/when
+ * @aggr_hard_byte_limit_en: whether aggregation frames close *before*
+ * 			     byte count has crossed limit, rather than
+ * 			     after XXX producer only?
+ */
+struct ipa_reg_endp_init_aggr {
+	u32 aggr_en;		/* enum ipa_aggr_en */
+	u32 aggr_type;		/* enum ipa_aggr_type */
+	u32 aggr_byte_limit;
+	u32 aggr_time_limit;
+	u32 aggr_pkt_limit;
+	u32 aggr_sw_eof_active;
+	u32 aggr_force_close;
+	u32 aggr_hard_byte_limit_en;
+};
+
+/**
+ * struct ipa_aggr_force_close - IPA_AGGR_FORCE_CLOSE field structure
+ * @pipe_bitmap: bitmap of pipes on which aggregation should be closed
+ */
+struct ipa_reg_aggr_force_close {
+	u32 pipe_bitmap;
+};
+
+/**
+ * enum ipa_mode - mode setting type in IPA end-point
+ * @BASIC: basic mode
+ * @ENABLE_FRAMING_HDLC: not currently supported
+ * @ENABLE_DEFRAMING_HDLC: not currently supported
+ * @DMA: all data arriving IPA will not go through IPA logic blocks, this
+ *  allows IPA to work as DMA for specific pipes.
+ */
+enum ipa_mode {
+	IPA_BASIC			= 0,
+	IPA_ENABLE_FRAMING_HDLC		= 1,
+	IPA_ENABLE_DEFRAMING_HDLC	= 2,
+	IPA_DMA				= 3,
+};
+
+/**
+ * struct ipa_reg_endp_init_mode - IPA_ENDP_INIT_MODE_N field structure
+ *
+ * @mode: endpoint mode setting (enum ipa_mode_type)
+ * @dst_pipe_index: This parameter specifies destination output-pipe-packets
+ *	will be routed to. Valid for DMA mode only and for Input
+ *	Pipes only (IPA Consumer)
+ * @byte_threshold:
+ * @pipe_replication_en:
+ * @pad_en:
+ * @hdr_ftch_disable:
+ */
+struct ipa_reg_endp_init_mode {
+	u32 mode;		/* enum ipa_mode */
+	u32 dest_pipe_index;
+	u32 byte_threshold;
+	u32 pipe_replication_en;
+	u32 pad_en;
+	u32 hdr_ftch_disable;
+};
+
+/**
+ * struct ipa_ep_init_ctrl - IPA_ENDP_INIT_CTRL_N field structure
+ *
+ * @ipa_ep_suspend: 0 - ENDP is enabled, 1 - ENDP is suspended (disabled).
+ *			Valid for PROD Endpoints
+ * @ipa_ep_delay:   0 - ENDP is free-running, 1 - ENDP is delayed.
+ *			SW controls the data flow of an endpoint usind this bit.
+ *			Valid for CONS Endpoints
+ */
+struct ipa_reg_endp_init_ctrl {
+	u32 endp_suspend;
+	u32 endp_delay;
+};
+
+/**
+ * struct ipa_reg_endp_init_deaggr - IPA_ENDP_INIT_DEAGGR_N field structure
+ *
+ * @deaggr_hdr_len:
+ * @packet_offset_valid:
+ * @packet_offset_location:
+ * @max_packet_len:
+ */
+struct ipa_reg_endp_init_deaggr {
+	u32 deaggr_hdr_len;
+	u32 packet_offset_valid;
+	u32 packet_offset_location;
+	u32 max_packet_len;
+};
+
+/* HPS, DPS sequencers types */
+enum ipa_seq_type {
+	IPA_SEQ_DMA_ONLY			= 0x00,
+	/* Packet Processing + no decipher + uCP (for Ethernet Bridging) */
+	IPA_SEQ_PKT_PROCESS_NO_DEC_UCP		= 0x02,
+	/* 2 Packet Processing pass + no decipher + uCP */
+	IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP	= 0x04,
+	/* DMA + DECIPHER/CIPHER */
+	IPA_SEQ_DMA_DEC				= 0x11,
+	/* COMP/DECOMP */
+	IPA_SEQ_DMA_COMP_DECOMP			= 0x20,
+	/* Invalid sequencer type */
+	IPA_SEQ_INVALID				= 0xff,
+};
+
+/**
+ * struct ipa_ep_init_seq - IPA_ENDP_INIT_SEQ_N field structure
+ * @hps_seq_type: type of HPS sequencer (enum ipa_hps_dps_sequencer_type)
+ * @dps_seq_type: type of DPS sequencer (enum ipa_hps_dps_sequencer_type)
+ */
+struct ipa_reg_endp_init_seq {
+	u32 hps_seq_type;
+	u32 dps_seq_type;
+	u32 hps_rep_seq_type;
+	u32 dps_rep_seq_type;
+};
+
+/**
+ * enum ipa_cs_offload_en - checksum offload setting
+ */
+enum ipa_cs_offload_en {
+	IPA_CS_OFFLOAD_NONE	= 0,
+	IPA_CS_OFFLOAD_UL	= 1,
+	IPA_CS_OFFLOAD_DL	= 2,
+	IPA_CS_RSVD
+};
+
+/**
+ * struct ipa_reg_endp_init_cfg - IPA_ENDP_INIT_CFG_N field structure
+ * @frag_offload_en:
+ * @cs_offload_en: type of offloading (enum ipa_cs_offload)
+ * @cs_metadata_hdr_offset: offload (in 4-byte words) within header
+ * where 4-byte checksum metadata begins.  Valid only for consumer
+ * pipes.
+ * @cs_gen_qmb_master_sel:
+ */
+struct ipa_reg_endp_init_cfg {
+	u32 frag_offload_en;
+	u32 cs_offload_en;		/* enum ipa_cs_offload_en */
+	u32 cs_metadata_hdr_offset;
+	u32 cs_gen_qmb_master_sel;
+};
+
+/**
+ * struct ipa_reg_endp_init_hdr_metadata_mask -
+ *	IPA_ENDP_INIT_HDR_METADATA_MASK_N field structure
+ * @metadata_mask: mask specifying metadata bits to write
+ *
+ * Valid for producer pipes only.
+ */
+struct ipa_reg_endp_init_hdr_metadata_mask {
+	u32 metadata_mask;
+};
+
+/**
+ * struct ipa_reg_shared_mem_size - SHARED_MEM_SIZE field structure
+ * @shared_mem_size: Available size [in 8Bytes] of SW partition within
+ *	IPA shared memory.
+ * @shared_mem_baddr: Offset of SW partition within IPA
+ *	shared memory[in 8Bytes]. To get absolute address of SW partition,
+ *	add this offset to IPA_SRAM_DIRECT_ACCESS_N baddr.
+ */
+struct ipa_reg_shared_mem_size {
+	u32 shared_mem_size;
+	u32 shared_mem_baddr;
+};
+
+/**
+ * struct ipa_reg_endp_status - IPA_ENDP_STATUS_N field structure
+ * @status_en: Determines if end point supports Status Indications. SW should
+ *	set this bit in order to enable Statuses. Output Pipe - send
+ *	Status indications only if bit is set. Input Pipe - forward Status
+ *	indication to STATUS_ENDP only if bit is set. Valid for Input
+ *	and Output Pipes (IPA Consumer and Producer)
+ * @status_endp: Statuses generated for this endpoint will be forwarded to the
+ *	specified Status End Point. Status endpoint needs to be
+ *	configured with STATUS_EN=1 Valid only for Input Pipes (IPA
+ *	Consumer)
+ * @status_location: Location of PKT-STATUS on destination pipe.
+ *	If set to 0 (default), PKT-STATUS will be appended before the packet
+ *	for this endpoint. If set to 1, PKT-STATUS will be appended after the
+ *	packet for this endpoint. Valid only for Output Pipes (IPA Producer)
+ * @status_pkt_suppress:
+ */
+struct ipa_reg_endp_status {
+	u32 status_en;
+	u32 status_endp;
+	u32 status_location;
+	u32 status_pkt_suppress;
+};
+
+/**
+ * struct ipa_hash_tuple - structure used to group filter and route fields in
+ *			   struct ipa_ep_filter_router_hsh_cfg
+ * @src_id: pipe number for flt, table index for rt
+ * @src_ip_addr: IP source address
+ * @dst_ip_addr: IP destination address
+ * @src_port: L4 source port
+ * @dst_port: L4 destination port
+ * @protocol: IP protocol field
+ * @meta_data: packet meta-data
+ *
+ * Each field is a Boolean value, indicating whether that particular value
+ * should be used for filtering or routing.
+ *
+ */
+struct ipa_reg_hash_tuple {
+	u32 src_id;	/* pipe number in flt, table index in rt */
+	u32 src_ip;
+	u32 dst_ip;
+	u32 src_port;
+	u32 dst_port;
+	u32 protocol;
+	u32 metadata;
+	u32 undefined;
+};
+
+/**
+ * struct ipa_ep_filter_router_hsh_cfg - IPA_ENDP_FILTER_ROUTER_HSH_CFG_N
+ * 					 field structure
+ * @flt: Hash tuple info for filtering
+ * @undefined1:
+ * @rt: Hash tuple info for routing
+ * @undefined2:
+ * @undefinedX: Undefined/Unused bit fields set of the register
+ */
+struct ipa_ep_filter_router_hsh_cfg {
+	struct ipa_reg_hash_tuple flt;
+	struct ipa_reg_hash_tuple rt;
+};
+
+/**
+ * struct ipa_reg_rsrc_grp_xy_rsrc_type_n -
+ *    IPA_{SRC,DST}_RSRC_GRP_{02}{13}Y_RSRC_TYPE_N field structure
+ * @x_min - first group min value
+ * @x_max - first group max value
+ * @y_min - second group min value
+ * @y_max - second group max value
+ *
+ * This field structure is used for accessing the following registers:
+ *	IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N IPA_SRC_RSRC_GRP_23_RSRC_TYPE_N
+ *	IPA_DST_RSRC_GRP_01_RSRC_TYPE_N IPA_DST_RSRC_GRP_23_RSRC_TYPE_N
+ *
+ */
+struct ipa_reg_rsrc_grp_xy_rsrc_type_n {
+	u32 x_min;
+	u32 x_max;
+	u32 y_min;
+	u32 y_max;
+};
+
+/**
+ * struct ipa_reg_qsb_max_writes - IPA_QSB_MAX_WRITES field register
+ * @qmb_0_max_writes: Max number of outstanding writes for GEN_QMB_0
+ * @qmb_1_max_writes: Max number of outstanding writes for GEN_QMB_1
+ */
+struct ipa_reg_qsb_max_writes {
+	u32 qmb_0_max_writes;
+	u32 qmb_1_max_writes;
+};
+
+/**
+ * struct ipa_reg_qsb_max_reads - IPA_QSB_MAX_READS field register
+ * @qmb_0_max_reads: Max number of outstanding reads for GEN_QMB_0
+ * @qmb_1_max_reads: Max number of outstanding reads for GEN_QMB_1
+ */
+struct ipa_reg_qsb_max_reads {
+	u32 qmb_0_max_reads;
+	u32 qmb_1_max_reads;
+};
+
+/** struct ipa_reg_idle_indication_cfg - IPA_IDLE_INDICATION_CFG field register
+ * @enter_idle_debounce_thresh:	 configure the debounce threshold
+ * @const_non_idle_enable: enable the asserting of the IDLE value and DCD
+ */
+struct ipa_reg_idle_indication_cfg {
+	u32 enter_idle_debounce_thresh;
+	u32 const_non_idle_enable;
+};
+
+/* Initialize the IPA register subsystem */
+int ipa_reg_init(phys_addr_t phys_addr, size_t size);
+void ipa_reg_exit(void);
+
+void ipa_reg_route(struct ipa_reg_route *route, u32 ep_id);
+void ipa_reg_endp_init_hdr_cons(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset);
+void ipa_reg_endp_init_hdr_prod(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset);
+void ipa_reg_endp_init_hdr_ext_cons(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align, bool pad_included);
+void ipa_reg_endp_init_hdr_ext_prod(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align);
+void ipa_reg_endp_init_aggr_cons(struct ipa_reg_endp_init_aggr *init_aggr,
+				 u32 byte_limit, u32 packet_limit,
+				 bool close_on_eof);
+void ipa_reg_endp_init_aggr_prod(struct ipa_reg_endp_init_aggr *init_aggr,
+				 enum ipa_aggr_en aggr_en,
+				 enum ipa_aggr_type aggr_type);
+void ipa_reg_aggr_force_close(struct ipa_reg_aggr_force_close *force_close,
+			      u32 pipe_bitmap);
+void ipa_reg_endp_init_mode_cons(struct ipa_reg_endp_init_mode *init_mode);
+void ipa_reg_endp_init_mode_prod(struct ipa_reg_endp_init_mode *init_mode,
+				 enum ipa_mode mode, u32 dest_endp);
+void ipa_reg_endp_init_cfg_cons(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type);
+void ipa_reg_endp_init_cfg_prod(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type,
+				u32 metadata_offset);
+void ipa_reg_endp_init_ctrl(struct ipa_reg_endp_init_ctrl *init_ctrl,
+			    bool suspend);
+void ipa_reg_endp_init_deaggr_cons(
+		struct ipa_reg_endp_init_deaggr *init_deaggr);
+void ipa_reg_endp_init_deaggr_prod(
+		struct ipa_reg_endp_init_deaggr *init_deaggr);
+void ipa_reg_endp_init_seq_cons(struct ipa_reg_endp_init_seq *init_seq);
+void ipa_reg_endp_init_seq_prod(struct ipa_reg_endp_init_seq *init_seq,
+				enum ipa_seq_type seq_type);
+void ipa_reg_endp_init_hdr_metadata_mask_cons(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask,
+		u32 mask);
+void ipa_reg_endp_init_hdr_metadata_mask_prod(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask);
+void ipa_reg_endp_status_cons(struct ipa_reg_endp_status *endp_status,
+			      bool enable);
+void ipa_reg_endp_status_prod(struct ipa_reg_endp_status *endp_status,
+			      bool enable, u32 endp);
+
+void ipa_reg_hash_tuple(struct ipa_reg_hash_tuple *tuple);
+
+void ipa_reg_rsrc_grp_xy_rsrc_type_n(
+				struct ipa_reg_rsrc_grp_xy_rsrc_type_n *limits,
+				u32 x_min, u32 x_max, u32 y_min, u32 y_max);
+
+void ipa_reg_qsb_max_writes(struct ipa_reg_qsb_max_writes *max_writes,
+			    u32 qmb_0_max_writes, u32 qmb_1_max_writes);
+void ipa_reg_qsb_max_reads(struct ipa_reg_qsb_max_reads *max_reads,
+			   u32 qmb_0_max_reads, u32 qmb_1_max_reads);
+
+void ipa_reg_idle_indication_cfg(struct ipa_reg_idle_indication_cfg *indication,
+			         u32 debounce_thresh, bool non_idle_enable);
+
+/* Get the offset of an n-parameterized register */
+u32 ipa_reg_n_offset(enum ipa_reg reg, u32 n);
+
+/* Get the offset of a register */
+static inline u32 ipa_reg_offset(enum ipa_reg reg)
+{
+	return ipa_reg_n_offset(reg, 0);
+}
+
+/* ipa_read_reg_n() - Get the raw value of n-parameterized register */
+u32 ipa_read_reg_n(enum ipa_reg reg, u32 n);
+
+/* ipa_write_reg_n() - Write a raw value to an n-param register */
+void ipa_write_reg_n(enum ipa_reg reg, u32 n, u32 val);
+
+/* ipa_read_reg_n_fields() - Get the parsed value of an n-param register */
+void ipa_read_reg_n_fields(enum ipa_reg reg, u32 n, void *fields);
+
+/* ipa_write_reg_n_fields() - Write a parsed value to an n-param register */
+void ipa_write_reg_n_fields(enum ipa_reg reg, u32 n, const void *fields);
+
+/* ipa_read_reg() - Get the raw value from a register */
+static inline u32 ipa_read_reg(enum ipa_reg reg)
+{
+	return ipa_read_reg_n(reg, 0);
+}
+
+/* ipa_write_reg() - Write a raw value to a register*/
+static inline void ipa_write_reg(enum ipa_reg reg, u32 val)
+{
+	ipa_write_reg_n(reg, 0, val);
+}
+
+/* ipa_read_reg_fields() - Get the parsed value of a register */
+static inline void ipa_read_reg_fields(enum ipa_reg reg, void *fields)
+{
+	ipa_read_reg_n_fields(reg, 0, fields);
+}
+
+/* ipa_write_reg_fields() - Write a parsed value to a register */
+static inline void ipa_write_reg_fields(enum ipa_reg reg, const void *fields)
+{
+	ipa_write_reg_n_fields(reg, 0, fields);
+}
+
+u32 ipa_reg_aggr_max_byte_limit(void);
+u32 ipa_reg_aggr_max_packet_limit(void);
+
+#endif /* _IPA_REG_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

(Much of the following is copied from text in "ipa_reg.c".  Please
see that for the more complete explanation.)

The IPA code abstracts the details of its 32-bit registers, allowing
access to them to be done generically.  The original motivation for
this was that the field width and/or position for values stored in
some registers differed for different versions of IPA hardware.
Abstracting access this way allows code that uses such registers to
be simpler, describing how register fields are used without
proliferating special-case code that is dependent on hardware
version.

Each IPA register has a name, which is one of the values in the
"ipa_reg" enumerated type (e.g., IPA_ENABLED_PIPES).  The offset
(memory address) of the register having a given name is maintained
internal to the "ipa_reg" module.

Some registers hold one or more fields that are less than 32 bits
wide.  Each of these registers has a data structure that breaks out
those fields into individual (32-bit) values.  These field structures
allow the register contents to be defined in a hardware independent
way.  Such registers have a pair of functions associated with them
to "construct" (when writing) and "parse" (when reading) the fields
found within them, using the register's fields structure.  This
allows the content of these registers to be read in a generic way.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_reg.c | 972 ++++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_reg.h | 614 ++++++++++++++++++++++++
 2 files changed, 1586 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_reg.c
 create mode 100644 drivers/net/ipa/ipa_reg.h

diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
new file mode 100644
index 000000000000..5e0aa6163235
--- /dev/null
+++ b/drivers/net/ipa/ipa_reg.c
@@ -0,0 +1,972 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/bitfield.h>
+
+#include "ipa_reg.h"
+
+/* I/O remapped base address of IPA register space */
+static void __iomem *ipa_reg_virt;
+
+/* struct ipa_reg_desc - descriptor for an abstracted hardware register
+ *
+ * @construct - fn to construct the register value from its field structure
+ * @parse - function to parse register field values into its field structure
+ * @offset - register offset relative to base address
+ * @n_ofst - size multiplier for "N-parameterized" registers
+ */
+struct ipa_reg_desc {
+	u32 (*construct)(enum ipa_reg reg, const void *fields);
+	void (*parse)(enum ipa_reg reg, void *fields, u32 val);
+	u32 offset;
+	u16 n_ofst;
+};
+
+/* IPA_ROUTE register */
+
+void ipa_reg_route(struct ipa_reg_route *route, u32 ep_id)
+{
+	route->route_dis = 0;
+	route->route_def_pipe = ep_id;
+	route->route_def_hdr_table = 1;
+	route->route_def_hdr_ofst = 0;
+	route->route_frag_def_pipe = ep_id;
+	route->route_def_retain_hdr = 1;
+}
+
+#define ROUTE_DIS_FMASK			0x00000001
+#define ROUTE_DEF_PIPE_FMASK		0x0000003e
+#define ROUTE_DEF_HDR_TABLE_FMASK	0x00000040
+#define ROUTE_DEF_HDR_OFST_FMASK	0x0001ff80
+#define ROUTE_FRAG_DEF_PIPE_FMASK	0x003e0000
+#define ROUTE_DEF_RETAIN_HDR_FMASK	0x01000000
+
+static u32 ipa_reg_construct_route(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_route *route = fields;
+	u32 val;
+
+	val = FIELD_PREP(ROUTE_DIS_FMASK, route->route_dis);
+	val |= FIELD_PREP(ROUTE_DEF_PIPE_FMASK, route->route_def_pipe);
+	val |= FIELD_PREP(ROUTE_DEF_HDR_TABLE_FMASK,
+			  route->route_def_hdr_table);
+	val |= FIELD_PREP(ROUTE_DEF_HDR_OFST_FMASK, route->route_def_hdr_ofst);
+	val |= FIELD_PREP(ROUTE_FRAG_DEF_PIPE_FMASK,
+			  route->route_frag_def_pipe);
+	val |= FIELD_PREP(ROUTE_DEF_RETAIN_HDR_FMASK,
+			  route->route_def_retain_hdr);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_HDR_N register */
+
+static void
+ipa_reg_endp_init_hdr_common(struct ipa_reg_endp_init_hdr *init_hdr)
+{
+	init_hdr->hdr_additional_const_len = 0;	/* XXX description? */
+	init_hdr->hdr_a5_mux = 0;		/* XXX description? */
+	init_hdr->hdr_len_inc_deagg_hdr = 0;	/* XXX description? */
+	init_hdr->hdr_metadata_reg_valid = 0;	/* XXX description? */
+}
+
+void ipa_reg_endp_init_hdr_cons(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset)
+{
+	init_hdr->hdr_len = header_size;
+	init_hdr->hdr_ofst_metadata_valid = 1;
+	init_hdr->hdr_ofst_metadata = metadata_offset;	/* XXX ignored */
+	init_hdr->hdr_ofst_pkt_size_valid = 1;
+	init_hdr->hdr_ofst_pkt_size = length_offset;
+
+	ipa_reg_endp_init_hdr_common(init_hdr);
+}
+
+void ipa_reg_endp_init_hdr_prod(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset)
+{
+	init_hdr->hdr_len = header_size;
+	init_hdr->hdr_ofst_metadata_valid = 1;
+	init_hdr->hdr_ofst_metadata = metadata_offset;
+	init_hdr->hdr_ofst_pkt_size_valid = 1;
+	init_hdr->hdr_ofst_pkt_size = length_offset;	/* XXX ignored */
+
+	ipa_reg_endp_init_hdr_common(init_hdr);
+}
+
+#define HDR_LEN_FMASK			0x0000003f
+#define HDR_OFST_METADATA_VALID_FMASK	0x00000040
+#define HDR_OFST_METADATA_FMASK		0x00001f80
+#define HDR_ADDITIONAL_CONST_LEN_FMASK	0x0007e000
+#define HDR_OFST_PKT_SIZE_VALID_FMASK	0x00080000
+#define HDR_OFST_PKT_SIZE_FMASK		0x03f00000
+#define HDR_A5_MUX_FMASK		0x04000000
+#define HDR_LEN_INC_DEAGG_HDR_FMASK	0x08000000
+#define HDR_METADATA_REG_VALID_FMASK	0x10000000
+
+static u32
+ipa_reg_construct_endp_init_hdr_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_hdr *init_hdr = fields;
+	u32 val;
+
+	val = FIELD_PREP(HDR_LEN_FMASK, init_hdr->hdr_len);
+	val |= FIELD_PREP(HDR_OFST_METADATA_VALID_FMASK,
+			  init_hdr->hdr_ofst_metadata_valid);
+	val |= FIELD_PREP(HDR_OFST_METADATA_FMASK, init_hdr->hdr_ofst_metadata);
+	val |= FIELD_PREP(HDR_ADDITIONAL_CONST_LEN_FMASK,
+			  init_hdr->hdr_additional_const_len);
+	val |= FIELD_PREP(HDR_OFST_PKT_SIZE_VALID_FMASK,
+			  init_hdr->hdr_ofst_pkt_size_valid);
+	val |= FIELD_PREP(HDR_OFST_PKT_SIZE_FMASK,
+			  init_hdr->hdr_ofst_pkt_size);
+	val |= FIELD_PREP(HDR_A5_MUX_FMASK, init_hdr->hdr_a5_mux);
+	val |= FIELD_PREP(HDR_LEN_INC_DEAGG_HDR_FMASK,
+			  init_hdr->hdr_len_inc_deagg_hdr);
+	val |= FIELD_PREP(HDR_METADATA_REG_VALID_FMASK,
+			  init_hdr->hdr_metadata_reg_valid);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_HDR_EXT_N register */
+
+void ipa_reg_endp_init_hdr_ext_common(struct ipa_reg_endp_init_hdr_ext *hdr_ext)
+{
+	hdr_ext->hdr_endianness = 1;			/* big endian */
+	hdr_ext->hdr_total_len_or_pad_valid = 1;
+	hdr_ext->hdr_total_len_or_pad = 0;		/* pad */
+	hdr_ext->hdr_total_len_or_pad_offset = 0;	/* XXX description? */
+}
+
+void ipa_reg_endp_init_hdr_ext_cons(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align, bool pad_included)
+{
+	hdr_ext->hdr_payload_len_inc_padding = pad_included ? 1 : 0;
+	hdr_ext->hdr_pad_to_alignment = pad_align;
+
+	ipa_reg_endp_init_hdr_ext_common(hdr_ext);
+}
+
+void ipa_reg_endp_init_hdr_ext_prod(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align)
+{
+	hdr_ext->hdr_payload_len_inc_padding = 0;
+	hdr_ext->hdr_pad_to_alignment = pad_align;	/* XXX ignored */
+
+	ipa_reg_endp_init_hdr_ext_common(hdr_ext);
+}
+
+#define HDR_ENDIANNESS_FMASK			0x00000001
+#define HDR_TOTAL_LEN_OR_PAD_VALID_FMASK	0x00000002
+#define HDR_TOTAL_LEN_OR_PAD_FMASK		0x00000004
+#define HDR_PAYLOAD_LEN_INC_PADDING_FMASK	0x00000008
+#define HDR_TOTAL_LEN_OR_PAD_OFFSET_FMASK	0x000003f0
+#define HDR_PAD_TO_ALIGNMENT_FMASK		0x00003c00
+
+static u32
+ipa_reg_construct_endp_init_hdr_ext_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_hdr_ext *init_hdr_ext = fields;
+	u32 val;
+
+	/* 0 = little endian; 1 = big endian */
+	val = FIELD_PREP(HDR_ENDIANNESS_FMASK, 1);
+	val |= FIELD_PREP(HDR_TOTAL_LEN_OR_PAD_VALID_FMASK,
+			  init_hdr_ext->hdr_total_len_or_pad_valid);
+	val |= FIELD_PREP(HDR_TOTAL_LEN_OR_PAD_FMASK,
+			  init_hdr_ext->hdr_total_len_or_pad);
+	val |= FIELD_PREP(HDR_PAYLOAD_LEN_INC_PADDING_FMASK,
+			  init_hdr_ext->hdr_payload_len_inc_padding);
+	val |= FIELD_PREP(HDR_TOTAL_LEN_OR_PAD_OFFSET_FMASK, 0);
+	val |= FIELD_PREP(HDR_PAD_TO_ALIGNMENT_FMASK,
+			  init_hdr_ext->hdr_pad_to_alignment);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_AGGR_N register */
+
+static void
+ipa_reg_endp_init_aggr_common(struct ipa_reg_endp_init_aggr *init_aggr)
+{
+	init_aggr->aggr_force_close = 0;	/* XXX description?  */
+	init_aggr->aggr_hard_byte_limit_en = 0;	/* XXX ignored for PROD? */
+}
+
+void ipa_reg_endp_init_aggr_cons(struct ipa_reg_endp_init_aggr *init_aggr,
+				 u32 byte_limit, u32 packet_limit,
+				 bool close_on_eof)
+{
+	init_aggr->aggr_en = IPA_ENABLE_AGGR;
+	init_aggr->aggr_type = IPA_GENERIC;
+	init_aggr->aggr_byte_limit = byte_limit;
+	init_aggr->aggr_time_limit = IPA_AGGR_TIME_LIMIT_DEFAULT;
+	init_aggr->aggr_pkt_limit = packet_limit;
+	init_aggr->aggr_sw_eof_active = close_on_eof ? 1 : 0;
+
+	ipa_reg_endp_init_aggr_common(init_aggr);
+}
+
+void ipa_reg_endp_init_aggr_prod(struct ipa_reg_endp_init_aggr *init_aggr,
+				 enum ipa_aggr_en aggr_en,
+				 enum ipa_aggr_type aggr_type)
+{
+	init_aggr->aggr_en = (u32)aggr_en;
+	init_aggr->aggr_type = aggr_en == IPA_BYPASS_AGGR ? 0 : (u32)aggr_type;
+	init_aggr->aggr_byte_limit = 0;		/* ignored */
+	init_aggr->aggr_time_limit = 0;		/* ignored */
+	init_aggr->aggr_pkt_limit = 0;		/* ignored */
+	init_aggr->aggr_sw_eof_active = 0;	/* ignored */
+
+	ipa_reg_endp_init_aggr_common(init_aggr);
+}
+
+#define AGGR_EN_FMASK				0x00000003
+#define AGGR_TYPE_FMASK				0x0000001c
+#define AGGR_BYTE_LIMIT_FMASK			0x000003e0
+#define AGGR_TIME_LIMIT_FMASK			0x00007c00
+#define AGGR_PKT_LIMIT_FMASK			0x001f8000
+#define AGGR_SW_EOF_ACTIVE_FMASK		0x00200000
+#define AGGR_FORCE_CLOSE_FMASK			0x00400000
+#define AGGR_HARD_BYTE_LIMIT_ENABLE_FMASK	0x01000000
+
+static u32
+ipa_reg_construct_endp_init_aggr_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_aggr *init_aggr = fields;
+	u32 val;
+
+	val = FIELD_PREP(AGGR_EN_FMASK, init_aggr->aggr_en);
+	val |= FIELD_PREP(AGGR_TYPE_FMASK, init_aggr->aggr_type);
+	val |= FIELD_PREP(AGGR_BYTE_LIMIT_FMASK, init_aggr->aggr_byte_limit);
+	val |= FIELD_PREP(AGGR_TIME_LIMIT_FMASK, init_aggr->aggr_time_limit);
+	val |= FIELD_PREP(AGGR_PKT_LIMIT_FMASK, init_aggr->aggr_pkt_limit);
+	val |= FIELD_PREP(AGGR_SW_EOF_ACTIVE_FMASK,
+			  init_aggr->aggr_sw_eof_active);
+	val |= FIELD_PREP(AGGR_FORCE_CLOSE_FMASK, init_aggr->aggr_force_close);
+	val |= FIELD_PREP(AGGR_HARD_BYTE_LIMIT_ENABLE_FMASK,
+			  init_aggr->aggr_hard_byte_limit_en);
+
+	return val;
+}
+
+static void
+ipa_reg_parse_endp_init_aggr_n(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_reg_endp_init_aggr *init_aggr = fields;
+
+	memset(init_aggr, 0, sizeof(*init_aggr));
+
+	init_aggr->aggr_en = FIELD_GET(AGGR_EN_FMASK, val);
+	init_aggr->aggr_type = FIELD_GET(AGGR_TYPE_FMASK, val);
+	init_aggr->aggr_byte_limit = FIELD_GET(AGGR_BYTE_LIMIT_FMASK, val);
+	init_aggr->aggr_time_limit = FIELD_GET(AGGR_TIME_LIMIT_FMASK, val);
+	init_aggr->aggr_pkt_limit = FIELD_GET(AGGR_PKT_LIMIT_FMASK, val);
+	init_aggr->aggr_sw_eof_active =
+			FIELD_GET(AGGR_SW_EOF_ACTIVE_FMASK, val);
+	init_aggr->aggr_force_close = FIELD_GET(AGGR_SW_EOF_ACTIVE_FMASK, val);
+	init_aggr->aggr_hard_byte_limit_en =
+			FIELD_GET(AGGR_HARD_BYTE_LIMIT_ENABLE_FMASK, val);
+}
+
+/* IPA_AGGR_FORCE_CLOSE register */
+
+void ipa_reg_aggr_force_close(struct ipa_reg_aggr_force_close *force_close,
+			      u32 pipe_bitmap)
+{
+	force_close->pipe_bitmap = pipe_bitmap;
+}
+
+#define PIPE_BITMAP_FMASK	0x000fffff
+
+static u32
+ipa_reg_construct_aggr_force_close(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_aggr_force_close *force_close = fields;
+
+	return FIELD_PREP(PIPE_BITMAP_FMASK, force_close->pipe_bitmap);
+}
+
+/* IPA_ENDP_INIT_MODE_N register */
+
+static void
+ipa_reg_endp_init_mode_common(struct ipa_reg_endp_init_mode *init_mode)
+{
+	init_mode->byte_threshold = 0;		/* XXX description? */
+	init_mode->pipe_replication_en = 0;	/* XXX description? */
+	init_mode->pad_en = 0;			/* XXX description? */
+	init_mode->hdr_ftch_disable = 0;	/* XXX description? */
+}
+
+/* IPA_ENDP_INIT_MODE is not valid for consumer pipes */
+void ipa_reg_endp_init_mode_cons(struct ipa_reg_endp_init_mode *init_mode)
+{
+	init_mode->mode = 0;            /* ignored */
+	init_mode->dest_pipe_index = 0; /* ignored */
+
+	ipa_reg_endp_init_mode_common(init_mode);
+}
+
+void ipa_reg_endp_init_mode_prod(struct ipa_reg_endp_init_mode *init_mode,
+				 enum ipa_mode mode, u32 dest_endp)
+{
+	init_mode->mode = mode;
+	init_mode->dest_pipe_index = mode == IPA_DMA ? dest_endp : 0;
+
+	ipa_reg_endp_init_mode_common(init_mode);
+}
+
+#define MODE_FMASK			0x00000007
+#define DEST_PIPE_INDEX_FMASK		0x000001f0
+#define BYTE_THRESHOLD_FMASK		0x0ffff000
+#define PIPE_REPLICATION_EN_FMASK	0x10000000
+#define PAD_EN_FMASK			0x20000000
+#define HDR_FTCH_DISABLE_FMASK		0x40000000
+
+static u32
+ipa_reg_construct_endp_init_mode_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_mode *init_mode = fields;
+	u32 val;
+
+	val = FIELD_PREP(MODE_FMASK, init_mode->mode);
+	val |= FIELD_PREP(DEST_PIPE_INDEX_FMASK, init_mode->dest_pipe_index);
+	val |= FIELD_PREP(BYTE_THRESHOLD_FMASK, init_mode->byte_threshold);
+	val |= FIELD_PREP(PIPE_REPLICATION_EN_FMASK,
+			  init_mode->pipe_replication_en);
+	val |= FIELD_PREP(PAD_EN_FMASK, init_mode->pad_en);
+	val |= FIELD_PREP(HDR_FTCH_DISABLE_FMASK, init_mode->hdr_ftch_disable);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_CTRL_N register */
+
+void
+ipa_reg_endp_init_ctrl(struct ipa_reg_endp_init_ctrl *init_ctrl, bool suspend)
+{
+	init_ctrl->endp_suspend = suspend ? 1 : 0;
+	init_ctrl->endp_delay = 0;
+}
+
+#define ENDP_SUSPEND_FMASK	0x00000001
+#define ENDP_DELAY_FMASK	0x00000002
+
+static u32
+ipa_reg_construct_endp_init_ctrl_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_ctrl *init_ctrl = fields;
+	u32 val;
+
+	val = FIELD_PREP(ENDP_SUSPEND_FMASK, init_ctrl->endp_suspend);
+	val |= FIELD_PREP(ENDP_DELAY_FMASK, init_ctrl->endp_delay);
+
+	return val;
+}
+
+static void
+ipa_reg_parse_endp_init_ctrl_n(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_reg_endp_init_ctrl *init_ctrl = fields;
+
+	memset(init_ctrl, 0, sizeof(*init_ctrl));
+
+	init_ctrl->endp_suspend = FIELD_GET(ENDP_SUSPEND_FMASK, val);
+	init_ctrl->endp_delay = FIELD_GET(ENDP_DELAY_FMASK, val);
+}
+
+/* IPA_ENDP_INIT_DEAGGR_N register */
+
+static void
+ipa_reg_endp_init_deaggr_common(struct ipa_reg_endp_init_deaggr *init_deaggr)
+{
+	init_deaggr->deaggr_hdr_len = 0;		/* XXX description? */
+	init_deaggr->packet_offset_valid = 0;		/* XXX description? */
+	init_deaggr->packet_offset_location = 0;	/* XXX description? */
+	init_deaggr->max_packet_len = 0;		/* XXX description? */
+}
+
+/* XXX The deaggr setting seems not to be valid for consumer endpoints */
+void
+ipa_reg_endp_init_deaggr_cons(struct ipa_reg_endp_init_deaggr *init_deaggr)
+{
+	ipa_reg_endp_init_deaggr_common(init_deaggr);
+}
+
+void
+ipa_reg_endp_init_deaggr_prod(struct ipa_reg_endp_init_deaggr *init_deaggr)
+{
+	ipa_reg_endp_init_deaggr_common(init_deaggr);
+}
+
+#define DEAGGR_HDR_LEN_FMASK		0x0000003f
+#define PACKET_OFFSET_VALID_FMASK	0x00000080
+#define PACKET_OFFSET_LOCATION_FMASK	0x00003f00
+#define MAX_PACKET_LEN_FMASK		0xffff0000
+
+static u32
+ipa_reg_construct_endp_init_deaggr_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_deaggr *init_deaggr = fields;
+	u32 val;
+
+	/* fields value is completely ignored (can be NULL) */
+	val = FIELD_PREP(DEAGGR_HDR_LEN_FMASK, init_deaggr->deaggr_hdr_len);
+	val |= FIELD_PREP(PACKET_OFFSET_VALID_FMASK,
+			  init_deaggr->packet_offset_valid);
+	val |= FIELD_PREP(PACKET_OFFSET_LOCATION_FMASK,
+			  init_deaggr->packet_offset_location);
+	val |= FIELD_PREP(MAX_PACKET_LEN_FMASK,
+			  init_deaggr->max_packet_len);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_SEQ_N register */
+
+static void
+ipa_reg_endp_init_seq_common(struct ipa_reg_endp_init_seq *init_seq)
+{
+	init_seq->dps_seq_type = 0;	/* XXX description? */
+	init_seq->hps_rep_seq_type = 0;	/* XXX description? */
+	init_seq->dps_rep_seq_type = 0;	/* XXX description? */
+}
+
+void ipa_reg_endp_init_seq_cons(struct ipa_reg_endp_init_seq *init_seq)
+{
+	init_seq->hps_seq_type = 0;	/* ignored */
+
+	ipa_reg_endp_init_seq_common(init_seq);
+}
+
+void ipa_reg_endp_init_seq_prod(struct ipa_reg_endp_init_seq *init_seq,
+				enum ipa_seq_type seq_type)
+{
+	init_seq->hps_seq_type = (u32)seq_type;
+
+	ipa_reg_endp_init_seq_common(init_seq);
+}
+
+#define HPS_SEQ_TYPE_FMASK	0x0000000f
+#define DPS_SEQ_TYPE_FMASK	0x000000f0
+#define HPS_REP_SEQ_TYPE_FMASK	0x00000f00
+#define DPS_REP_SEQ_TYPE_FMASK	0x0000f000
+
+static u32
+ipa_reg_construct_endp_init_seq_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_seq *init_seq = fields;
+	u32 val;
+
+	val = FIELD_PREP(HPS_SEQ_TYPE_FMASK, init_seq->hps_seq_type);
+	val |= FIELD_PREP(DPS_SEQ_TYPE_FMASK, init_seq->dps_seq_type);
+	val |= FIELD_PREP(HPS_REP_SEQ_TYPE_FMASK, init_seq->hps_rep_seq_type);
+	val |= FIELD_PREP(DPS_REP_SEQ_TYPE_FMASK, init_seq->dps_rep_seq_type);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_CFG_N register */
+
+static void
+ipa_reg_endp_init_cfg_common(struct ipa_reg_endp_init_cfg *init_cfg)
+{
+	init_cfg->frag_offload_en = 0;		/* XXX description?  */
+	init_cfg->cs_gen_qmb_master_sel = 0;	/* XXX description?  */
+}
+
+void ipa_reg_endp_init_cfg_cons(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type)
+{
+	init_cfg->cs_offload_en = offload_type;
+	init_cfg->cs_metadata_hdr_offset = 0;	/* ignored */
+
+	ipa_reg_endp_init_cfg_common(init_cfg);
+}
+
+void ipa_reg_endp_init_cfg_prod(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type,
+				u32 metadata_offset)
+{
+	init_cfg->cs_offload_en = offload_type;
+	init_cfg->cs_metadata_hdr_offset = metadata_offset;
+
+	ipa_reg_endp_init_cfg_common(init_cfg);
+}
+
+#define FRAG_OFFLOAD_EN_FMASK		0x00000001
+#define CS_OFFLOAD_EN_FMASK		0x00000006
+#define CS_METADATA_HDR_OFFSET_FMASK	0x00000078
+#define CS_GEN_QMB_MASTER_SEL_FMASK	0x00000100
+
+static u32
+ipa_reg_construct_endp_init_cfg_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_init_cfg *init_cfg = fields;
+	u32 val;
+
+	val = FIELD_PREP(FRAG_OFFLOAD_EN_FMASK, init_cfg->frag_offload_en);
+	val |= FIELD_PREP(CS_OFFLOAD_EN_FMASK, init_cfg->cs_offload_en);
+	val |= FIELD_PREP(CS_METADATA_HDR_OFFSET_FMASK,
+			  init_cfg->cs_metadata_hdr_offset);
+	val |= FIELD_PREP(CS_GEN_QMB_MASTER_SEL_FMASK,
+			  init_cfg->cs_gen_qmb_master_sel);
+
+	return val;
+}
+
+/* IPA_ENDP_INIT_HDR_METADATA_MASK_N register */
+
+void ipa_reg_endp_init_hdr_metadata_mask_cons(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask,
+		u32 mask)
+{
+	metadata_mask->metadata_mask = mask;
+}
+
+/* IPA_ENDP_INIT_HDR_METADATA_MASK is not valid for producer pipes */
+void ipa_reg_endp_init_hdr_metadata_mask_prod(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask)
+{
+	metadata_mask->metadata_mask = 0;	/* ignored */
+}
+
+
+#define METADATA_MASK_FMASK	0xffffffff
+
+static u32 ipa_reg_construct_endp_init_hdr_metadata_mask_n(enum ipa_reg reg,
+							  const void *fields)
+{
+	const struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask;
+
+	metadata_mask = fields;
+
+	return FIELD_PREP(METADATA_MASK_FMASK, metadata_mask->metadata_mask);
+}
+
+/* IPA_SHARED_MEM_SIZE register (read-only) */
+
+#define SHARED_MEM_SIZE_FMASK	0x0000ffff
+#define SHARED_MEM_BADDR_FMASK	0xffff0000
+
+static void
+ipa_reg_parse_shared_mem_size(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_reg_shared_mem_size *mem_size = fields;
+
+	memset(mem_size, 0, sizeof(*mem_size));
+
+	mem_size->shared_mem_size = FIELD_GET(SHARED_MEM_SIZE_FMASK, val);
+	mem_size->shared_mem_baddr = FIELD_GET(SHARED_MEM_BADDR_FMASK, val);
+}
+
+/* IPA_ENDP_STATUS_N register */
+
+static void ipa_reg_endp_status_common(struct ipa_reg_endp_status *endp_status)
+{
+	endp_status->status_pkt_suppress = 0;	/* XXX description?  */
+}
+
+void ipa_reg_endp_status_cons(struct ipa_reg_endp_status *endp_status,
+			      bool enable)
+{
+	endp_status->status_en = enable ? 1 : 0;
+	endp_status->status_endp = 0;		/* ignored */
+	endp_status->status_location = 0;	/* before packet data */
+
+	ipa_reg_endp_status_common(endp_status);
+}
+
+void ipa_reg_endp_status_prod(struct ipa_reg_endp_status *endp_status,
+			      bool enable, u32 endp)
+{
+	endp_status->status_en = enable ? 1 : 0;
+	endp_status->status_endp = endp;
+	endp_status->status_location = 0;	/* ignored */
+
+	ipa_reg_endp_status_common(endp_status);
+}
+
+#define STATUS_EN_FMASK			0x00000001
+#define STATUS_ENDP_FMASK		0x0000003e
+#define STATUS_LOCATION_FMASK		0x00000100
+#define STATUS_PKT_SUPPRESS_FMASK	0x00000200
+
+static u32 ipa_reg_construct_endp_status_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_endp_status *endp_status = fields;
+	u32 val;
+
+	val = FIELD_PREP(STATUS_EN_FMASK, endp_status->status_en);
+	val |= FIELD_PREP(STATUS_ENDP_FMASK, endp_status->status_endp);
+	val |= FIELD_PREP(STATUS_LOCATION_FMASK, endp_status->status_location);
+	val |= FIELD_PREP(STATUS_PKT_SUPPRESS_FMASK, 0);
+
+	return val;
+}
+
+/* IPA_ENDP_FILTER_ROUTER_HSH_CFG_N register */
+
+void ipa_reg_hash_tuple(struct ipa_reg_hash_tuple *tuple)
+{
+	tuple->src_id = 0;	/* pipe number in flt, table index in rt */
+	tuple->src_ip = 0;
+	tuple->dst_ip = 0;
+	tuple->src_port = 0;
+	tuple->dst_port = 0;
+	tuple->protocol = 0;
+	tuple->metadata = 0;
+	tuple->undefined = 0;
+}
+
+#define FILTER_HASH_MSK_SRC_ID_FMASK	0x00000001
+#define FILTER_HASH_MSK_SRC_IP_FMASK	0x00000002
+#define FILTER_HASH_MSK_DST_IP_FMASK	0x00000004
+#define FILTER_HASH_MSK_SRC_PORT_FMASK	0x00000008
+#define FILTER_HASH_MSK_DST_PORT_FMASK	0x00000010
+#define FILTER_HASH_MSK_PROTOCOL_FMASK	0x00000020
+#define FILTER_HASH_MSK_METADATA_FMASK	0x00000040
+#define FILTER_HASH_UNDEFINED1_FMASK	0x0000ff80
+
+#define ROUTER_HASH_MSK_SRC_ID_FMASK	0x00010000
+#define ROUTER_HASH_MSK_SRC_IP_FMASK	0x00020000
+#define ROUTER_HASH_MSK_DST_IP_FMASK	0x00040000
+#define ROUTER_HASH_MSK_SRC_PORT_FMASK	0x00080000
+#define ROUTER_HASH_MSK_DST_PORT_FMASK	0x00100000
+#define ROUTER_HASH_MSK_PROTOCOL_FMASK	0x00200000
+#define ROUTER_HASH_MSK_METADATA_FMASK	0x00400000
+#define ROUTER_HASH_UNDEFINED2_FMASK	0xff800000
+
+static u32 ipa_reg_construct_hash_cfg_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_ep_filter_router_hsh_cfg *hsh_cfg = fields;
+	u32 val;
+
+	val = FIELD_PREP(FILTER_HASH_MSK_SRC_ID_FMASK, hsh_cfg->flt.src_id);
+	val |= FIELD_PREP(FILTER_HASH_MSK_SRC_IP_FMASK, hsh_cfg->flt.src_ip);
+	val |= FIELD_PREP(FILTER_HASH_MSK_DST_IP_FMASK, hsh_cfg->flt.dst_ip);
+	val |= FIELD_PREP(FILTER_HASH_MSK_SRC_PORT_FMASK,
+			  hsh_cfg->flt.src_port);
+	val |= FIELD_PREP(FILTER_HASH_MSK_DST_PORT_FMASK,
+			  hsh_cfg->flt.dst_port);
+	val |= FIELD_PREP(FILTER_HASH_MSK_PROTOCOL_FMASK,
+			  hsh_cfg->flt.protocol);
+	val |= FIELD_PREP(FILTER_HASH_MSK_METADATA_FMASK,
+			  hsh_cfg->flt.metadata);
+	val |= FIELD_PREP(FILTER_HASH_UNDEFINED1_FMASK, hsh_cfg->flt.undefined);
+
+	val |= FIELD_PREP(ROUTER_HASH_MSK_SRC_ID_FMASK, hsh_cfg->rt.src_id);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_SRC_IP_FMASK, hsh_cfg->rt.src_ip);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_DST_IP_FMASK, hsh_cfg->rt.dst_ip);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_SRC_PORT_FMASK, hsh_cfg->rt.src_port);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_DST_PORT_FMASK, hsh_cfg->rt.dst_port);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_PROTOCOL_FMASK, hsh_cfg->rt.protocol);
+	val |= FIELD_PREP(ROUTER_HASH_MSK_METADATA_FMASK, hsh_cfg->rt.metadata);
+	val |= FIELD_PREP(FILTER_HASH_UNDEFINED1_FMASK, hsh_cfg->rt.undefined);
+
+	return val;
+}
+
+static void ipa_reg_parse_hash_cfg_n(enum ipa_reg reg, void *fields, u32 val)
+{
+	struct ipa_ep_filter_router_hsh_cfg *hsh_cfg = fields;
+
+	memset(hsh_cfg, 0, sizeof(*hsh_cfg));
+
+	hsh_cfg->flt.src_id = FIELD_GET(FILTER_HASH_MSK_SRC_ID_FMASK, val);
+	hsh_cfg->flt.src_ip = FIELD_GET(FILTER_HASH_MSK_SRC_IP_FMASK, val);
+	hsh_cfg->flt.dst_ip = FIELD_GET(FILTER_HASH_MSK_DST_IP_FMASK, val);
+	hsh_cfg->flt.src_port = FIELD_GET(FILTER_HASH_MSK_SRC_PORT_FMASK, val);
+	hsh_cfg->flt.dst_port = FIELD_GET(FILTER_HASH_MSK_DST_PORT_FMASK, val);
+	hsh_cfg->flt.protocol = FIELD_GET(FILTER_HASH_MSK_PROTOCOL_FMASK, val);
+	hsh_cfg->flt.metadata = FIELD_GET(FILTER_HASH_MSK_METADATA_FMASK, val);
+	hsh_cfg->flt.undefined = FIELD_GET(FILTER_HASH_UNDEFINED1_FMASK, val);
+
+	hsh_cfg->rt.src_id = FIELD_GET(ROUTER_HASH_MSK_SRC_ID_FMASK, val);
+	hsh_cfg->rt.src_ip = FIELD_GET(ROUTER_HASH_MSK_SRC_IP_FMASK, val);
+	hsh_cfg->rt.dst_ip = FIELD_GET(ROUTER_HASH_MSK_DST_IP_FMASK, val);
+	hsh_cfg->rt.src_port = FIELD_GET(ROUTER_HASH_MSK_SRC_PORT_FMASK, val);
+	hsh_cfg->rt.dst_port = FIELD_GET(ROUTER_HASH_MSK_DST_PORT_FMASK, val);
+	hsh_cfg->rt.protocol = FIELD_GET(ROUTER_HASH_MSK_PROTOCOL_FMASK, val);
+	hsh_cfg->rt.metadata = FIELD_GET(ROUTER_HASH_MSK_METADATA_FMASK, val);
+	hsh_cfg->rt.undefined = FIELD_GET(ROUTER_HASH_UNDEFINED2_FMASK, val);
+}
+
+/* IPA_RSRC_GRP_XY_RSRC_TYPE_N register(s) */
+
+void
+ipa_reg_rsrc_grp_xy_rsrc_type_n(struct ipa_reg_rsrc_grp_xy_rsrc_type_n *limits,
+				 u32 x_min, u32 x_max, u32 y_min, u32 y_max)
+{
+	limits->x_min = x_min;
+	limits->x_max = x_max;
+	limits->y_min = y_min;
+	limits->y_max = y_max;
+}
+
+#define X_MIN_LIM_FMASK	0x0000003f
+#define X_MAX_LIM_FMASK	0x00003f00
+#define Y_MIN_LIM_FMASK	0x003f0000
+#define Y_MAX_LIM_FMASK	0x3f000000
+
+static u32
+ipa_reg_construct_rsrg_grp_xy_rsrc_type_n(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_rsrc_grp_xy_rsrc_type_n *limits = fields;
+	u32 val;
+
+	val = FIELD_PREP(X_MIN_LIM_FMASK, limits->x_min);
+	val |= FIELD_PREP(X_MAX_LIM_FMASK, limits->x_max);
+
+	/* DST_23 register has only X fields at ipa V3_5 */
+	if (reg == IPA_DST_RSRC_GRP_23_RSRC_TYPE_N)
+		return val;
+
+	val |= FIELD_PREP(Y_MIN_LIM_FMASK, limits->y_min);
+	val |= FIELD_PREP(Y_MAX_LIM_FMASK, limits->y_max);
+
+	return val;
+}
+
+/* IPA_QSB_MAX_WRITES register */
+
+void ipa_reg_qsb_max_writes(struct ipa_reg_qsb_max_writes *max_writes,
+			    u32 qmb_0_max_writes, u32 qmb_1_max_writes)
+{
+	max_writes->qmb_0_max_writes = qmb_0_max_writes;
+	max_writes->qmb_1_max_writes = qmb_1_max_writes;
+}
+
+#define GEN_QMB_0_MAX_WRITES_FMASK	0x0000000f
+#define GEN_QMB_1_MAX_WRITES_FMASK	0x000000f0
+
+static u32
+ipa_reg_construct_qsb_max_writes(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_qsb_max_writes *max_writes = fields;
+	u32 val;
+
+	val = FIELD_PREP(GEN_QMB_0_MAX_WRITES_FMASK,
+			  max_writes->qmb_0_max_writes);
+	val |= FIELD_PREP(GEN_QMB_1_MAX_WRITES_FMASK,
+			  max_writes->qmb_1_max_writes);
+
+	return val;
+}
+
+/* IPA_QSB_MAX_READS register */
+
+void ipa_reg_qsb_max_reads(struct ipa_reg_qsb_max_reads *max_reads,
+			   u32 qmb_0_max_reads, u32 qmb_1_max_reads)
+{
+	max_reads->qmb_0_max_reads = qmb_0_max_reads;
+	max_reads->qmb_1_max_reads = qmb_1_max_reads;
+}
+
+#define GEN_QMB_0_MAX_READS_FMASK	0x0000000f
+#define GEN_QMB_1_MAX_READS_FMASK	0x000000f0
+
+static u32 ipa_reg_construct_qsb_max_reads(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_qsb_max_reads *max_reads = fields;
+	u32 val;
+
+	val = FIELD_PREP(GEN_QMB_0_MAX_READS_FMASK, max_reads->qmb_0_max_reads);
+	val |= FIELD_PREP(GEN_QMB_1_MAX_READS_FMASK,
+			  max_reads->qmb_1_max_reads);
+
+	return val;
+}
+
+/* IPA_IDLE_INDICATION_CFG register */
+
+void ipa_reg_idle_indication_cfg(struct ipa_reg_idle_indication_cfg *indication,
+				 u32 debounce_thresh, bool non_idle_enable)
+{
+	indication->enter_idle_debounce_thresh = debounce_thresh;
+	indication->const_non_idle_enable = non_idle_enable;
+}
+
+#define ENTER_IDLE_DEBOUNCE_THRESH_FMASK	0x0000ffff
+#define CONST_NON_IDLE_ENABLE_FMASK		0x00010000
+
+static u32
+ipa_reg_construct_idle_indication_cfg(enum ipa_reg reg, const void *fields)
+{
+	const struct ipa_reg_idle_indication_cfg *indication_cfg;
+	u32 val;
+
+	indication_cfg = fields;
+
+	val = FIELD_PREP(ENTER_IDLE_DEBOUNCE_THRESH_FMASK,
+			  indication_cfg->enter_idle_debounce_thresh);
+	val |= FIELD_PREP(CONST_NON_IDLE_ENABLE_FMASK,
+			  indication_cfg->const_non_idle_enable);
+
+	return val;
+}
+
+/* The entries in the following table have the following constraints:
+ * - 0 is not a valid offset (it represents an unused entry).  It is
+ *   a bug for code to attempt to access a register which has an
+ *   undefined (zero) offset value.
+ * - If a construct function is supplied, the register must be
+ *   written using ipa_write_reg_n_fields() (or its wrapper
+ *   function ipa_write_reg_fields()).
+ * - Generally, if a parse function is supplied, the register should
+ *   read using ipa_read_reg_n_fields() (or ipa_read_reg_fields()).
+ *   (Currently some debug code reads some registers directly, without
+ *   parsing.)
+ */
+#define cfunc(f)	ipa_reg_construct_ ## f
+#define pfunc(f)	ipa_reg_parse_ ## f
+#define reg_obj_common(id, cf, pf, o, n)	\
+	[id] = {				\
+		.construct = cf,		\
+		.parse = pf,			\
+		.offset = o,			\
+		.n_ofst = n,			\
+	}
+#define reg_obj_cfunc(id, f, o, n)		\
+	reg_obj_common(id, cfunc(f), NULL, o, n)
+#define reg_obj_pfunc(id, f, o, n)		\
+	reg_obj_common(id, NULL, pfunc(f), o, n)
+#define reg_obj_both(id, f, o, n)		\
+	reg_obj_common(id, cfunc(f), pfunc(f), o, n)
+#define reg_obj_nofunc(id, o, n)		\
+	reg_obj_common(id, NULL, NULL, o, n)
+
+/* IPAv3.5.1 */
+static const struct ipa_reg_desc ipa_reg[] = {
+	reg_obj_cfunc(IPA_ROUTE, route,			0x00000048,	0x0000),
+	reg_obj_nofunc(IPA_IRQ_STTS_EE_N,		0x00003008,	0x1000),
+	reg_obj_nofunc(IPA_IRQ_EN_EE_N,			0x0000300c,	0x1000),
+	reg_obj_nofunc(IPA_IRQ_CLR_EE_N,		0x00003010,	0x1000),
+	reg_obj_nofunc(IPA_IRQ_SUSPEND_INFO_EE_N,	0x00003030,	0x1000),
+	reg_obj_nofunc(IPA_SUSPEND_IRQ_EN_EE_N,		0x00003034,	0x1000),
+	reg_obj_nofunc(IPA_SUSPEND_IRQ_CLR_EE_N,	0x00003038,	0x1000),
+	reg_obj_nofunc(IPA_BCR,				0x000001d0,	0x0000),
+	reg_obj_nofunc(IPA_ENABLED_PIPES,		0x00000038,	0x0000),
+	reg_obj_nofunc(IPA_TAG_TIMER,			0x00000060,	0x0000),
+	reg_obj_nofunc(IPA_STATE_AGGR_ACTIVE,		0x0000010c,	0x0000),
+	reg_obj_cfunc(IPA_ENDP_INIT_HDR_N,
+		      endp_init_hdr_n,			0x00000810,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_HDR_EXT_N,
+		      endp_init_hdr_ext_n,		0x00000814,	0x0070),
+	reg_obj_both(IPA_ENDP_INIT_AGGR_N,
+		     endp_init_aggr_n,			0x00000824,	0x0070),
+	reg_obj_cfunc(IPA_AGGR_FORCE_CLOSE,
+		     aggr_force_close,			0x000001ec,	0x0000),
+	reg_obj_cfunc(IPA_ENDP_INIT_MODE_N,
+		      endp_init_mode_n,			0x00000820,	0x0070),
+	reg_obj_both(IPA_ENDP_INIT_CTRL_N,
+		     endp_init_ctrl_n,			0x00000800,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_DEAGGR_N,
+		      endp_init_deaggr_n,		0x00000834,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_SEQ_N,
+		      endp_init_seq_n,			0x0000083c,	0x0070),
+	reg_obj_cfunc(IPA_ENDP_INIT_CFG_N,
+		      endp_init_cfg_n,			0x00000808,	0x0070),
+	reg_obj_nofunc(IPA_IRQ_EE_UC_N,			0x0000301c,	0x1000),
+	reg_obj_cfunc(IPA_ENDP_INIT_HDR_METADATA_MASK_N,
+		      endp_init_hdr_metadata_mask_n,	0x00000818,	0x0070),
+	reg_obj_pfunc(IPA_SHARED_MEM_SIZE,
+		      shared_mem_size,			0x00000054,	0x0000),
+	reg_obj_nofunc(IPA_SRAM_DIRECT_ACCESS_N,	0x00007000,	0x0004),
+	reg_obj_nofunc(IPA_LOCAL_PKT_PROC_CNTXT_BASE,	0x000001e8,	0x0000),
+	reg_obj_cfunc(IPA_ENDP_STATUS_N,
+		      endp_status_n,			0x00000840,	0x0070),
+	reg_obj_both(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N,
+		     hash_cfg_n,			0x0000085c,	0x0070),
+	reg_obj_cfunc(IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000400,	0x0020),
+	reg_obj_cfunc(IPA_SRC_RSRC_GRP_23_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000404,	0x0020),
+	reg_obj_cfunc(IPA_DST_RSRC_GRP_01_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000500,	0x0020),
+	reg_obj_cfunc(IPA_DST_RSRC_GRP_23_RSRC_TYPE_N,
+		      rsrg_grp_xy_rsrc_type_n,		0x00000504,	0x0020),
+	reg_obj_cfunc(IPA_QSB_MAX_WRITES,
+		      qsb_max_writes,			0x00000074,	0x0000),
+	reg_obj_cfunc(IPA_QSB_MAX_READS,
+		      qsb_max_reads,			0x00000078,	0x0000),
+	reg_obj_cfunc(IPA_IDLE_INDICATION_CFG,
+		      idle_indication_cfg,		0x00000220,	0x0000),
+};
+
+#undef reg_obj_nofunc
+#undef reg_obj_both
+#undef reg_obj_pfunc
+#undef reg_obj_cfunc
+#undef reg_obj_common
+#undef pfunc
+#undef cfunc
+
+int ipa_reg_init(phys_addr_t phys_addr, size_t size)
+{
+	ipa_reg_virt = ioremap(phys_addr, size);
+
+	return ipa_reg_virt ? 0 : -ENOMEM;
+}
+
+void ipa_reg_exit(void)
+{
+	iounmap(ipa_reg_virt);
+	ipa_reg_virt = NULL;
+}
+
+/* Get the offset of an "n parameterized" register */
+u32 ipa_reg_n_offset(enum ipa_reg reg, u32 n)
+{
+	return ipa_reg[reg].offset + n * ipa_reg[reg].n_ofst;
+}
+
+/* ipa_read_reg_n() - Get an "n parameterized" register's value */
+u32 ipa_read_reg_n(enum ipa_reg reg, u32 n)
+{
+	return ioread32(ipa_reg_virt + ipa_reg_n_offset(reg, n));
+}
+
+/* ipa_write_reg_n() - Write a raw value to an "n parameterized" register */
+void ipa_write_reg_n(enum ipa_reg reg, u32 n, u32 val)
+{
+	iowrite32(val, ipa_reg_virt + ipa_reg_n_offset(reg, n));
+}
+
+/* ipa_read_reg_n_fields() - Parse value of an "n parameterized" register */
+void ipa_read_reg_n_fields(enum ipa_reg reg, u32 n, void *fields)
+{
+	u32 val = ipa_read_reg_n(reg, n);
+
+	ipa_reg[reg].parse(reg, fields, val);
+}
+
+/* ipa_write_reg_n_fields() - Construct a vlaue to write to an "n
+ * parameterized" register
+ */
+void ipa_write_reg_n_fields(enum ipa_reg reg, u32 n, const void *fields)
+{
+	u32 val = ipa_reg[reg].construct(reg, fields);
+
+	ipa_write_reg_n(reg, n, val);
+}
+
+/* Maximum representable aggregation byte limit value (in bytes) */
+u32 ipa_reg_aggr_max_byte_limit(void)
+{
+	return FIELD_MAX(AGGR_BYTE_LIMIT_FMASK) * SZ_1K;
+}
+
+/* Maximum representable aggregation packet limit value */
+u32 ipa_reg_aggr_max_packet_limit(void)
+{
+	return FIELD_MAX(AGGR_PKT_LIMIT_FMASK);
+}
diff --git a/drivers/net/ipa/ipa_reg.h b/drivers/net/ipa/ipa_reg.h
new file mode 100644
index 000000000000..fb7c1ab6408c
--- /dev/null
+++ b/drivers/net/ipa/ipa_reg.h
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_REG_H_
+#define _IPA_REG_H_
+
+/**
+ * DOC: The IPA Register Abstraction
+ *
+ * The IPA code abstracts the details of its 32-bit registers, allowing access
+ * to them to be done generically.  The original motivation for this was that
+ * the field width and/or position for values stored in some registers differed
+ * for different versions of IPA hardware.  Abstracting access this way allows
+ * code that uses such registers to be simpler, describing how register fields
+ * are used without proliferating special-case code that is dependent on
+ * hardware version.
+ *
+ * Each IPA register has a name, which is one of the values in the "ipa_reg"
+ * enumerated type (e.g., IPA_ENABLED_PIPES).  The offset (memory address) of
+ * the register having a given name is maintained internal to the "ipa_reg"
+ * module.
+ *
+ * For simple registers that hold a single 32-bit value, two functions provide
+ * access to the register:
+ *	u32 ipa_read_reg(enum ipa_reg reg);
+ *	void ipa_write_reg(enum ipa_reg reg, u32 val);
+ *
+ * Some registers are "N-parameterized."  This means there is a set of
+ * registers having identical format, and each is accessed by supplying
+ * the "N" value to select which register is intended.  The names for
+ * N-parameterized registers have an "_N" suffix (e.g. IPA_IRQ_STTS_EE_N).
+ * Details of computing the offset for such registers are maintained internal
+ * to the "ipa_reg" module.  For simple registers holding a single 32-bit
+ * value, these functions provide access to N-parameterized registers:
+ *	u32 ipa_read_reg_n(enum ipa_reg reg, u32 n);
+ *	void ipa_write_reg_n(enum ipa_reg reg, u32 n, u32 val);
+ *
+ * Some registers contain fields less than 32 bits wide (call these "field
+ * registers").  For each such register a "field structure" is defined to
+ * represent the values of the individual fields within the register.  The
+ * name of the structure matches the name of the register (in lower case).
+ * For example, the individual fields in the IPA_ROUTE register are represented
+ * by the field structure named ipa_reg_route.
+ *
+ * Each field register has a function used to fill in its corresponding
+ * field structure with particular values.  Parameters to this function
+ * supply values to assign.  In many cases only a few such parameters
+ * are required, because some field values are invariant.  The name of
+ * this sort of function is derived from the structure name, so for example
+ * ipa_reg_route() is used to initialize an ipa_reg_route structure.
+ * Field registers associated with endpoints often use different fields
+ * or different values dependent on whether the endpoint is a producer or
+ * consumer.  In these cases separate functions are used to initialize
+ * the field structure (for example ipa_reg_endp_init_hdr_cons() and
+ * ipa_reg_endp_init_hdr_prod()).
+ *
+ * The position and width of fields within a register are defined (in
+ * "ipa_reg.c") using field masks, and the names of the members in the field
+ * structure associated with such registers match the names of the bit masks
+ * that define the fields.  (E.g., ipa_reg_route->route_dis is used to
+ * represent the field defined by the ROUTE_DIS field mask.)
+ *
+ * "Field registers" are accessed using these functions:
+ *	void ipa_read_reg_fields(enum ipa_reg reg, void *fields);
+ *	void ipa_write_reg_fields(enum ipa_reg reg, const void *fields);
+ * The "fields" parameter in both cases is the address of the "field structure"
+ * associated with the register being accessed.  When reading, the structure
+ * is filled by ipa_read_reg_fields() with values found in the register's
+ * fields.  (All fields will be filled; there is no need for the caller to
+ * initialize the passed-in structure before the call.)  When writing, the
+ * caller initializes the structure with all values that should be written to
+ * the fields in the register.
+ *
+ * "Field registers" can also be N-parameterized, in which case they are
+ * accessed using these functions:
+ *	void ipa_read_reg_n_fields(enum ipa_reg reg, u32 n, void *fields);
+ *	void ipa_write_reg_n_fields(enum ipa_reg reg, u32 n,
+ *				    const void *fields);
+ */
+
+/* Register names */
+enum ipa_reg {
+	IPA_ROUTE,
+	IPA_IRQ_STTS_EE_N,
+	IPA_IRQ_EN_EE_N,
+	IPA_IRQ_CLR_EE_N,
+	IPA_IRQ_SUSPEND_INFO_EE_N,
+	IPA_SUSPEND_IRQ_EN_EE_N,
+	IPA_SUSPEND_IRQ_CLR_EE_N,
+	IPA_BCR,
+	IPA_ENABLED_PIPES,
+	IPA_TAG_TIMER,
+	IPA_STATE_AGGR_ACTIVE,
+	IPA_ENDP_INIT_HDR_N,
+	IPA_ENDP_INIT_HDR_EXT_N,
+	IPA_ENDP_INIT_AGGR_N,
+	IPA_AGGR_FORCE_CLOSE,
+	IPA_ENDP_INIT_MODE_N,
+	IPA_ENDP_INIT_CTRL_N,
+	IPA_ENDP_INIT_DEAGGR_N,
+	IPA_ENDP_INIT_SEQ_N,
+	IPA_ENDP_INIT_CFG_N,
+	IPA_IRQ_EE_UC_N,
+	IPA_ENDP_INIT_HDR_METADATA_MASK_N,
+	IPA_SHARED_MEM_SIZE,
+	IPA_SRAM_DIRECT_ACCESS_N,
+	IPA_LOCAL_PKT_PROC_CNTXT_BASE,
+	IPA_ENDP_STATUS_N,
+	IPA_ENDP_FILTER_ROUTER_HSH_CFG_N,
+	IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N,
+	IPA_SRC_RSRC_GRP_23_RSRC_TYPE_N,
+	IPA_DST_RSRC_GRP_01_RSRC_TYPE_N,
+	IPA_DST_RSRC_GRP_23_RSRC_TYPE_N,
+	IPA_QSB_MAX_WRITES,
+	IPA_QSB_MAX_READS,
+	IPA_IDLE_INDICATION_CFG,
+};
+
+/**
+ * struct ipa_reg_route - IPA_ROUTE field structure
+ * @route_dis: route disable
+ * @route_def_pipe: route default pipe
+ * @route_def_hdr_table: route default header table
+ * @route_def_hdr_ofst: route default header offset table
+ * @route_frag_def_pipe: Default pipe to route fragmented exception
+ *    packets and frag new rule statues, if source pipe does not have
+ *    a notification status pipe defined.
+ * @route_def_retain_hdr: default value of retain header. It is used
+ *    when no rule was hit
+ */
+struct ipa_reg_route {
+	u32 route_dis;
+	u32 route_def_pipe;
+	u32 route_def_hdr_table;
+	u32 route_def_hdr_ofst;
+	u32 route_frag_def_pipe;
+	u32 route_def_retain_hdr;
+};
+
+/**
+ * ipa_reg_endp_init_hdr - ENDP_INIT_HDR_N field structure
+ *
+ * @hdr_len:
+ * @hdr_ofst_metadata_valid:
+ * @hdr_ofst_metadata:
+ * @hdr_additional_const_len:
+ * @hdr_ofst_pkt_size_valid:
+ * @hdr_ofst_pkt_size:
+ * @hdr_a5_mux:
+ * @hdr_len_inc_deagg_hdr:
+ * @hdr_metadata_reg_valid:
+*/
+struct ipa_reg_endp_init_hdr {
+	u32 hdr_len;
+	u32 hdr_ofst_metadata_valid;
+	u32 hdr_ofst_metadata;
+	u32 hdr_additional_const_len;
+	u32 hdr_ofst_pkt_size_valid;
+	u32 hdr_ofst_pkt_size;
+	u32 hdr_a5_mux;
+	u32 hdr_len_inc_deagg_hdr;
+	u32 hdr_metadata_reg_valid;
+};
+
+/**
+ * ipa_reg_endp_init_hdr_ext - IPA_ENDP_INIT_HDR_EXT_N field structure
+ *
+ * @hdr_endianness:
+ * @hdr_total_len_or_pad_valid:
+ * @hdr_total_len_or_pad:
+ * @hdr_payload_len_inc_padding:
+ * @hdr_total_len_or_pad_offset:
+ * @hdr_pad_to_alignment:
+ */
+struct ipa_reg_endp_init_hdr_ext {
+	u32 hdr_endianness;		/* 0 = little endian; 1 = big endian */
+	u32 hdr_total_len_or_pad_valid;
+	u32 hdr_total_len_or_pad;	/* 0 = pad; 1 = total_len */
+	u32 hdr_payload_len_inc_padding;
+	u32 hdr_total_len_or_pad_offset;
+	u32 hdr_pad_to_alignment;
+};
+
+/**
+ * enum ipa_aggr_en - aggregation setting type in IPA end-point
+ */
+enum ipa_aggr_en {
+	IPA_BYPASS_AGGR		= 0,
+	IPA_ENABLE_AGGR		= 1,
+	IPA_ENABLE_DEAGGR	= 2,
+};
+
+/**
+ * enum ipa_aggr_type - type of aggregation in IPA end-point
+ */
+enum ipa_aggr_type {
+	IPA_MBIM_16 = 0,
+	IPA_HDLC    = 1,
+	IPA_TLP	    = 2,
+	IPA_RNDIS   = 3,
+	IPA_GENERIC = 4,
+	IPA_QCMAP   = 6,
+};
+
+#define IPA_AGGR_TIME_LIMIT_DEFAULT	1	/* XXX units? */
+
+/**
+ * struct ipa_reg_endp_init_aggr - IPA_ENDP_INIT_AGGR_N field structure
+ * @aggr_en: bypass aggregation, enable aggregation, or deaggregation
+ *	     (enum ipa_aggr_en)
+ * @aggr_type: type of aggregation (enum ipa_aggr_type aggr)
+ * @aggr_byte_limit: aggregated byte limit in KB, or no limit if 0
+ *		     (producer pipes only)
+ * @aggr_time_limit: time limit before close of aggregation, or
+ *		     aggregation disabled if 0 (producer pipes only)
+ * @aggr_pkt_limit: packet limit before closing aggregation, or no
+ *		    limit if 0 (producer pipes only) XXX units
+ * @aggr_sw_eof_active: whether EOF closes aggregation--in addition to
+ *			hardware aggregation configuration (producer
+ *			pipes configured for generic aggregation only)
+ * @aggr_force_close: whether to force a close XXX verify/when
+ * @aggr_hard_byte_limit_en: whether aggregation frames close *before*
+ * 			     byte count has crossed limit, rather than
+ * 			     after XXX producer only?
+ */
+struct ipa_reg_endp_init_aggr {
+	u32 aggr_en;		/* enum ipa_aggr_en */
+	u32 aggr_type;		/* enum ipa_aggr_type */
+	u32 aggr_byte_limit;
+	u32 aggr_time_limit;
+	u32 aggr_pkt_limit;
+	u32 aggr_sw_eof_active;
+	u32 aggr_force_close;
+	u32 aggr_hard_byte_limit_en;
+};
+
+/**
+ * struct ipa_aggr_force_close - IPA_AGGR_FORCE_CLOSE field structure
+ * @pipe_bitmap: bitmap of pipes on which aggregation should be closed
+ */
+struct ipa_reg_aggr_force_close {
+	u32 pipe_bitmap;
+};
+
+/**
+ * enum ipa_mode - mode setting type in IPA end-point
+ * @BASIC: basic mode
+ * @ENABLE_FRAMING_HDLC: not currently supported
+ * @ENABLE_DEFRAMING_HDLC: not currently supported
+ * @DMA: all data arriving IPA will not go through IPA logic blocks, this
+ *  allows IPA to work as DMA for specific pipes.
+ */
+enum ipa_mode {
+	IPA_BASIC			= 0,
+	IPA_ENABLE_FRAMING_HDLC		= 1,
+	IPA_ENABLE_DEFRAMING_HDLC	= 2,
+	IPA_DMA				= 3,
+};
+
+/**
+ * struct ipa_reg_endp_init_mode - IPA_ENDP_INIT_MODE_N field structure
+ *
+ * @mode: endpoint mode setting (enum ipa_mode_type)
+ * @dst_pipe_index: This parameter specifies destination output-pipe-packets
+ *	will be routed to. Valid for DMA mode only and for Input
+ *	Pipes only (IPA Consumer)
+ * @byte_threshold:
+ * @pipe_replication_en:
+ * @pad_en:
+ * @hdr_ftch_disable:
+ */
+struct ipa_reg_endp_init_mode {
+	u32 mode;		/* enum ipa_mode */
+	u32 dest_pipe_index;
+	u32 byte_threshold;
+	u32 pipe_replication_en;
+	u32 pad_en;
+	u32 hdr_ftch_disable;
+};
+
+/**
+ * struct ipa_ep_init_ctrl - IPA_ENDP_INIT_CTRL_N field structure
+ *
+ * @ipa_ep_suspend: 0 - ENDP is enabled, 1 - ENDP is suspended (disabled).
+ *			Valid for PROD Endpoints
+ * @ipa_ep_delay:   0 - ENDP is free-running, 1 - ENDP is delayed.
+ *			SW controls the data flow of an endpoint usind this bit.
+ *			Valid for CONS Endpoints
+ */
+struct ipa_reg_endp_init_ctrl {
+	u32 endp_suspend;
+	u32 endp_delay;
+};
+
+/**
+ * struct ipa_reg_endp_init_deaggr - IPA_ENDP_INIT_DEAGGR_N field structure
+ *
+ * @deaggr_hdr_len:
+ * @packet_offset_valid:
+ * @packet_offset_location:
+ * @max_packet_len:
+ */
+struct ipa_reg_endp_init_deaggr {
+	u32 deaggr_hdr_len;
+	u32 packet_offset_valid;
+	u32 packet_offset_location;
+	u32 max_packet_len;
+};
+
+/* HPS, DPS sequencers types */
+enum ipa_seq_type {
+	IPA_SEQ_DMA_ONLY			= 0x00,
+	/* Packet Processing + no decipher + uCP (for Ethernet Bridging) */
+	IPA_SEQ_PKT_PROCESS_NO_DEC_UCP		= 0x02,
+	/* 2 Packet Processing pass + no decipher + uCP */
+	IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP	= 0x04,
+	/* DMA + DECIPHER/CIPHER */
+	IPA_SEQ_DMA_DEC				= 0x11,
+	/* COMP/DECOMP */
+	IPA_SEQ_DMA_COMP_DECOMP			= 0x20,
+	/* Invalid sequencer type */
+	IPA_SEQ_INVALID				= 0xff,
+};
+
+/**
+ * struct ipa_ep_init_seq - IPA_ENDP_INIT_SEQ_N field structure
+ * @hps_seq_type: type of HPS sequencer (enum ipa_hps_dps_sequencer_type)
+ * @dps_seq_type: type of DPS sequencer (enum ipa_hps_dps_sequencer_type)
+ */
+struct ipa_reg_endp_init_seq {
+	u32 hps_seq_type;
+	u32 dps_seq_type;
+	u32 hps_rep_seq_type;
+	u32 dps_rep_seq_type;
+};
+
+/**
+ * enum ipa_cs_offload_en - checksum offload setting
+ */
+enum ipa_cs_offload_en {
+	IPA_CS_OFFLOAD_NONE	= 0,
+	IPA_CS_OFFLOAD_UL	= 1,
+	IPA_CS_OFFLOAD_DL	= 2,
+	IPA_CS_RSVD
+};
+
+/**
+ * struct ipa_reg_endp_init_cfg - IPA_ENDP_INIT_CFG_N field structure
+ * @frag_offload_en:
+ * @cs_offload_en: type of offloading (enum ipa_cs_offload)
+ * @cs_metadata_hdr_offset: offload (in 4-byte words) within header
+ * where 4-byte checksum metadata begins.  Valid only for consumer
+ * pipes.
+ * @cs_gen_qmb_master_sel:
+ */
+struct ipa_reg_endp_init_cfg {
+	u32 frag_offload_en;
+	u32 cs_offload_en;		/* enum ipa_cs_offload_en */
+	u32 cs_metadata_hdr_offset;
+	u32 cs_gen_qmb_master_sel;
+};
+
+/**
+ * struct ipa_reg_endp_init_hdr_metadata_mask -
+ *	IPA_ENDP_INIT_HDR_METADATA_MASK_N field structure
+ * @metadata_mask: mask specifying metadata bits to write
+ *
+ * Valid for producer pipes only.
+ */
+struct ipa_reg_endp_init_hdr_metadata_mask {
+	u32 metadata_mask;
+};
+
+/**
+ * struct ipa_reg_shared_mem_size - SHARED_MEM_SIZE field structure
+ * @shared_mem_size: Available size [in 8Bytes] of SW partition within
+ *	IPA shared memory.
+ * @shared_mem_baddr: Offset of SW partition within IPA
+ *	shared memory[in 8Bytes]. To get absolute address of SW partition,
+ *	add this offset to IPA_SRAM_DIRECT_ACCESS_N baddr.
+ */
+struct ipa_reg_shared_mem_size {
+	u32 shared_mem_size;
+	u32 shared_mem_baddr;
+};
+
+/**
+ * struct ipa_reg_endp_status - IPA_ENDP_STATUS_N field structure
+ * @status_en: Determines if end point supports Status Indications. SW should
+ *	set this bit in order to enable Statuses. Output Pipe - send
+ *	Status indications only if bit is set. Input Pipe - forward Status
+ *	indication to STATUS_ENDP only if bit is set. Valid for Input
+ *	and Output Pipes (IPA Consumer and Producer)
+ * @status_endp: Statuses generated for this endpoint will be forwarded to the
+ *	specified Status End Point. Status endpoint needs to be
+ *	configured with STATUS_EN=1 Valid only for Input Pipes (IPA
+ *	Consumer)
+ * @status_location: Location of PKT-STATUS on destination pipe.
+ *	If set to 0 (default), PKT-STATUS will be appended before the packet
+ *	for this endpoint. If set to 1, PKT-STATUS will be appended after the
+ *	packet for this endpoint. Valid only for Output Pipes (IPA Producer)
+ * @status_pkt_suppress:
+ */
+struct ipa_reg_endp_status {
+	u32 status_en;
+	u32 status_endp;
+	u32 status_location;
+	u32 status_pkt_suppress;
+};
+
+/**
+ * struct ipa_hash_tuple - structure used to group filter and route fields in
+ *			   struct ipa_ep_filter_router_hsh_cfg
+ * @src_id: pipe number for flt, table index for rt
+ * @src_ip_addr: IP source address
+ * @dst_ip_addr: IP destination address
+ * @src_port: L4 source port
+ * @dst_port: L4 destination port
+ * @protocol: IP protocol field
+ * @meta_data: packet meta-data
+ *
+ * Each field is a Boolean value, indicating whether that particular value
+ * should be used for filtering or routing.
+ *
+ */
+struct ipa_reg_hash_tuple {
+	u32 src_id;	/* pipe number in flt, table index in rt */
+	u32 src_ip;
+	u32 dst_ip;
+	u32 src_port;
+	u32 dst_port;
+	u32 protocol;
+	u32 metadata;
+	u32 undefined;
+};
+
+/**
+ * struct ipa_ep_filter_router_hsh_cfg - IPA_ENDP_FILTER_ROUTER_HSH_CFG_N
+ * 					 field structure
+ * @flt: Hash tuple info for filtering
+ * @undefined1:
+ * @rt: Hash tuple info for routing
+ * @undefined2:
+ * @undefinedX: Undefined/Unused bit fields set of the register
+ */
+struct ipa_ep_filter_router_hsh_cfg {
+	struct ipa_reg_hash_tuple flt;
+	struct ipa_reg_hash_tuple rt;
+};
+
+/**
+ * struct ipa_reg_rsrc_grp_xy_rsrc_type_n -
+ *    IPA_{SRC,DST}_RSRC_GRP_{02}{13}Y_RSRC_TYPE_N field structure
+ * @x_min - first group min value
+ * @x_max - first group max value
+ * @y_min - second group min value
+ * @y_max - second group max value
+ *
+ * This field structure is used for accessing the following registers:
+ *	IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N IPA_SRC_RSRC_GRP_23_RSRC_TYPE_N
+ *	IPA_DST_RSRC_GRP_01_RSRC_TYPE_N IPA_DST_RSRC_GRP_23_RSRC_TYPE_N
+ *
+ */
+struct ipa_reg_rsrc_grp_xy_rsrc_type_n {
+	u32 x_min;
+	u32 x_max;
+	u32 y_min;
+	u32 y_max;
+};
+
+/**
+ * struct ipa_reg_qsb_max_writes - IPA_QSB_MAX_WRITES field register
+ * @qmb_0_max_writes: Max number of outstanding writes for GEN_QMB_0
+ * @qmb_1_max_writes: Max number of outstanding writes for GEN_QMB_1
+ */
+struct ipa_reg_qsb_max_writes {
+	u32 qmb_0_max_writes;
+	u32 qmb_1_max_writes;
+};
+
+/**
+ * struct ipa_reg_qsb_max_reads - IPA_QSB_MAX_READS field register
+ * @qmb_0_max_reads: Max number of outstanding reads for GEN_QMB_0
+ * @qmb_1_max_reads: Max number of outstanding reads for GEN_QMB_1
+ */
+struct ipa_reg_qsb_max_reads {
+	u32 qmb_0_max_reads;
+	u32 qmb_1_max_reads;
+};
+
+/** struct ipa_reg_idle_indication_cfg - IPA_IDLE_INDICATION_CFG field register
+ * @enter_idle_debounce_thresh:	 configure the debounce threshold
+ * @const_non_idle_enable: enable the asserting of the IDLE value and DCD
+ */
+struct ipa_reg_idle_indication_cfg {
+	u32 enter_idle_debounce_thresh;
+	u32 const_non_idle_enable;
+};
+
+/* Initialize the IPA register subsystem */
+int ipa_reg_init(phys_addr_t phys_addr, size_t size);
+void ipa_reg_exit(void);
+
+void ipa_reg_route(struct ipa_reg_route *route, u32 ep_id);
+void ipa_reg_endp_init_hdr_cons(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset);
+void ipa_reg_endp_init_hdr_prod(struct ipa_reg_endp_init_hdr *init_hdr,
+				u32 header_size, u32 metadata_offset,
+				u32 length_offset);
+void ipa_reg_endp_init_hdr_ext_cons(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align, bool pad_included);
+void ipa_reg_endp_init_hdr_ext_prod(struct ipa_reg_endp_init_hdr_ext *hdr_ext,
+				    u32 pad_align);
+void ipa_reg_endp_init_aggr_cons(struct ipa_reg_endp_init_aggr *init_aggr,
+				 u32 byte_limit, u32 packet_limit,
+				 bool close_on_eof);
+void ipa_reg_endp_init_aggr_prod(struct ipa_reg_endp_init_aggr *init_aggr,
+				 enum ipa_aggr_en aggr_en,
+				 enum ipa_aggr_type aggr_type);
+void ipa_reg_aggr_force_close(struct ipa_reg_aggr_force_close *force_close,
+			      u32 pipe_bitmap);
+void ipa_reg_endp_init_mode_cons(struct ipa_reg_endp_init_mode *init_mode);
+void ipa_reg_endp_init_mode_prod(struct ipa_reg_endp_init_mode *init_mode,
+				 enum ipa_mode mode, u32 dest_endp);
+void ipa_reg_endp_init_cfg_cons(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type);
+void ipa_reg_endp_init_cfg_prod(struct ipa_reg_endp_init_cfg *init_cfg,
+				enum ipa_cs_offload_en offload_type,
+				u32 metadata_offset);
+void ipa_reg_endp_init_ctrl(struct ipa_reg_endp_init_ctrl *init_ctrl,
+			    bool suspend);
+void ipa_reg_endp_init_deaggr_cons(
+		struct ipa_reg_endp_init_deaggr *init_deaggr);
+void ipa_reg_endp_init_deaggr_prod(
+		struct ipa_reg_endp_init_deaggr *init_deaggr);
+void ipa_reg_endp_init_seq_cons(struct ipa_reg_endp_init_seq *init_seq);
+void ipa_reg_endp_init_seq_prod(struct ipa_reg_endp_init_seq *init_seq,
+				enum ipa_seq_type seq_type);
+void ipa_reg_endp_init_hdr_metadata_mask_cons(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask,
+		u32 mask);
+void ipa_reg_endp_init_hdr_metadata_mask_prod(
+		struct ipa_reg_endp_init_hdr_metadata_mask *metadata_mask);
+void ipa_reg_endp_status_cons(struct ipa_reg_endp_status *endp_status,
+			      bool enable);
+void ipa_reg_endp_status_prod(struct ipa_reg_endp_status *endp_status,
+			      bool enable, u32 endp);
+
+void ipa_reg_hash_tuple(struct ipa_reg_hash_tuple *tuple);
+
+void ipa_reg_rsrc_grp_xy_rsrc_type_n(
+				struct ipa_reg_rsrc_grp_xy_rsrc_type_n *limits,
+				u32 x_min, u32 x_max, u32 y_min, u32 y_max);
+
+void ipa_reg_qsb_max_writes(struct ipa_reg_qsb_max_writes *max_writes,
+			    u32 qmb_0_max_writes, u32 qmb_1_max_writes);
+void ipa_reg_qsb_max_reads(struct ipa_reg_qsb_max_reads *max_reads,
+			   u32 qmb_0_max_reads, u32 qmb_1_max_reads);
+
+void ipa_reg_idle_indication_cfg(struct ipa_reg_idle_indication_cfg *indication,
+			         u32 debounce_thresh, bool non_idle_enable);
+
+/* Get the offset of an n-parameterized register */
+u32 ipa_reg_n_offset(enum ipa_reg reg, u32 n);
+
+/* Get the offset of a register */
+static inline u32 ipa_reg_offset(enum ipa_reg reg)
+{
+	return ipa_reg_n_offset(reg, 0);
+}
+
+/* ipa_read_reg_n() - Get the raw value of n-parameterized register */
+u32 ipa_read_reg_n(enum ipa_reg reg, u32 n);
+
+/* ipa_write_reg_n() - Write a raw value to an n-param register */
+void ipa_write_reg_n(enum ipa_reg reg, u32 n, u32 val);
+
+/* ipa_read_reg_n_fields() - Get the parsed value of an n-param register */
+void ipa_read_reg_n_fields(enum ipa_reg reg, u32 n, void *fields);
+
+/* ipa_write_reg_n_fields() - Write a parsed value to an n-param register */
+void ipa_write_reg_n_fields(enum ipa_reg reg, u32 n, const void *fields);
+
+/* ipa_read_reg() - Get the raw value from a register */
+static inline u32 ipa_read_reg(enum ipa_reg reg)
+{
+	return ipa_read_reg_n(reg, 0);
+}
+
+/* ipa_write_reg() - Write a raw value to a register*/
+static inline void ipa_write_reg(enum ipa_reg reg, u32 val)
+{
+	ipa_write_reg_n(reg, 0, val);
+}
+
+/* ipa_read_reg_fields() - Get the parsed value of a register */
+static inline void ipa_read_reg_fields(enum ipa_reg reg, void *fields)
+{
+	ipa_read_reg_n_fields(reg, 0, fields);
+}
+
+/* ipa_write_reg_fields() - Write a parsed value to a register */
+static inline void ipa_write_reg_fields(enum ipa_reg reg, const void *fields)
+{
+	ipa_write_reg_n_fields(reg, 0, fields);
+}
+
+u32 ipa_reg_aggr_max_byte_limit(void);
+u32 ipa_reg_aggr_max_packet_limit(void);
+
+#endif /* _IPA_REG_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 08/12] soc: qcom: ipa: utility functions
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This patch contains "ipa_utils.c", which provides some miscellaneous
supporting code.  Included are:
  - Endpoint configuration.  The IPA hardware has a fixed number of
    endpoints (pipes) available.  In some cases the AP and modem
    must agree on aspects of these.  A key example is that they need
    to agree what particular endpoints are used for (the modem can't
    send messages to the AP command endpoint, for example).  There
    is a static array that defines these parameters, and this is
    found in "ipa_utils.c".
  - Resource group configuration.  The IPA has a number of internal
    resources it uses for its operation.  These are configurable,
    and it is up to the AP to set these configuration values at
    initialization time.  There are some static arrays that define
    these configuration values.  Functions are also defined here
    to send those values to hardware.
  - Shared memory.  The IPA uses a region of shared memory to hold
    various data structures.  A function ipa_sram_settings_read()
    fetches the location and size of this shared memory.  (The
    individual regions are currently initialized in "ipa_main.c".)
  - Endpoint configuration.  Each endpoint (or channel) has a number
    of configurable properties.  Functions found in this file are
    used to configure these properties.
  - Interconnect handling.  The IPA driver depends on the
    interconnect framework to request that buses it uses be enabled
    when needed.

This is not a complete list, but it covers much of the functionality
found in "ipa_utils.c".

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_utils.c | 1035 +++++++++++++++++++++++++++++++++++
 1 file changed, 1035 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_utils.c

diff --git a/drivers/net/ipa/ipa_utils.c b/drivers/net/ipa/ipa_utils.c
new file mode 100644
index 000000000000..085b0218779b
--- /dev/null
+++ b/drivers/net/ipa/ipa_utils.c
@@ -0,0 +1,1035 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "ipa_dma.h"
+#include "ipa_i.h"
+#include "ipahal.h"
+
+/* Interconnect path bandwidths (each times 1000 bytes per second) */
+#define IPA_MEMORY_AVG			80000
+#define IPA_MEMORY_PEAK			600000
+
+#define IPA_IMEM_AVG			80000
+#define IPA_IMEM_PEAK			350000
+
+#define IPA_CONFIG_AVG			40000
+#define IPA_CONFIG_PEAK			40000
+
+#define IPA_BCR_REG_VAL			0x0000003b
+
+#define IPA_GSI_DMA_TASK_TIMEOUT	15	/* milliseconds */
+
+#define IPA_GSI_CHANNEL_STOP_SLEEP_MIN	1000	/* microseconds */
+#define IPA_GSI_CHANNEL_STOP_SLEEP_MAX	2000	/* microseconds */
+
+#define QMB_MASTER_SELECT_DDR		0
+
+enum ipa_rsrc_group {
+	IPA_RSRC_GROUP_LWA_DL,	/* currently not used */
+	IPA_RSRC_GROUP_UL_DL,
+	IPA_RSRC_GROUP_MAX,
+};
+
+enum ipa_rsrc_grp_type_src {
+	IPA_RSRC_GRP_TYPE_SRC_PKT_CONTEXTS,
+	IPA_RSRC_GRP_TYPE_SRS_DESCRIPTOR_LISTS,
+	IPA_RSRC_GRP_TYPE_SRC_DESCRIPTOR_BUFF,
+	IPA_RSRC_GRP_TYPE_SRC_HPS_DMARS,
+	IPA_RSRC_GRP_TYPE_SRC_ACK_ENTRIES,
+};
+
+enum ipa_rsrc_grp_type_dst {
+	IPA_RSRC_GRP_TYPE_DST_DATA_SECTORS,
+	IPA_RSRC_GRP_TYPE_DST_DPS_DMARS,
+};
+
+enum ipa_rsrc_grp_type_rx {
+	IPA_RSRC_GRP_TYPE_RX_HPS_CMDQ,
+	IPA_RSRC_GRP_TYPE_RX_MAX
+};
+
+struct rsrc_min_max {
+	u32 min;
+	u32 max;
+};
+
+/* IPA_HW_v3_5_1 */
+static const struct rsrc_min_max ipa_src_rsrc_grp[][IPA_RSRC_GROUP_MAX] = {
+	[IPA_RSRC_GRP_TYPE_SRC_PKT_CONTEXTS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 1,	.max = 63, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 1,	.max = 63, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRS_DESCRIPTOR_LISTS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 10,	.max = 10, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 10,	.max = 10, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRC_DESCRIPTOR_BUFF] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 12,	.max = 12, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 14,	.max = 14, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRC_HPS_DMARS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 0,	.max = 63, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 0,	.max = 63, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRC_ACK_ENTRIES] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 14,	.max = 14, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 20,	.max = 20, },
+	},
+};
+
+/* IPA_HW_v3_5_1 */
+static const struct rsrc_min_max ipa_dst_rsrc_grp[][IPA_RSRC_GROUP_MAX] = {
+	[IPA_RSRC_GRP_TYPE_DST_DATA_SECTORS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 4,	.max = 4, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 4,	.max = 4, },
+	},
+	[IPA_RSRC_GRP_TYPE_DST_DPS_DMARS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 2,	.max = 63, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 1,	.max = 63, },
+	},
+};
+
+/**
+ * struct ipa_gsi_ep_config - GSI endpoint configuration.
+ * @ep_id:	IPA endpoint identifier.
+ * @channel_id:	GSI channel number used for this endpoint.
+ * @tlv_count:	The number of TLV (type-length-value) entries for the channel.
+ * @ee:		Execution environment endpoint is associated with.
+ *
+ * Each GSI endpoint has a set of configuration parameters defined within
+ * entries in the ipa_ep_configuration[] array.  Its @ep_id field uniquely
+ * defines the endpoint, and @channel_id defines which data channel (ring
+ * buffer) is used for the endpoint.
+ * XXX TLV
+ * XXX ee is never used in the code
+ */
+struct ipa_gsi_ep_config {
+	u32 ep_id;
+	u32 channel_id;
+	u32 tlv_count;
+	u32 ee;
+};
+
+struct ipa_ep_configuration {
+	bool support_flt;
+	enum ipa_seq_type seq_type;
+	struct ipa_gsi_ep_config ipa_gsi_ep_info;
+};
+
+/* IPA_HW_v3_5_1 */
+/* clients not included in the list below are considered as invalid */
+static const struct ipa_ep_configuration ipa_ep_configuration[] = {
+	[IPA_CLIENT_WLAN1_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 7,
+			.channel_id	= 1,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	[IPA_CLIENT_USB_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 0,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_LAN_PROD] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 8,
+			.channel_id	= 7,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_WAN_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 2,
+			.channel_id	= 3,
+			.tlv_count	= 16,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_CMD_PROD] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_DMA_ONLY,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 5,
+			.channel_id	= 4,
+			.tlv_count	= 20,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_Q6_LAN_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 3,
+			.channel_id	= 0,
+			.tlv_count	= 16,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_Q6_WAN_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 6,
+			.channel_id	= 4,
+			.tlv_count	= 12,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_Q6_CMD_PROD] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 4,
+			.channel_id	= 1,
+			.tlv_count	= 20,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_TEST_CONS] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 14,
+			.channel_id	= 5,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_TEST1_CONS] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 15,
+			.channel_id	= 2,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	/* Only for testing */
+	[IPA_CLIENT_TEST_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 0,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST1_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 0,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST2_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 2,
+			.channel_id	= 3,
+			.tlv_count	= 16,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST3_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 4,
+			.channel_id	= 1,
+			.tlv_count	= 20,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_TEST4_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 1,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	[IPA_CLIENT_WLAN1_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 16,
+			.channel_id	= 3,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	[IPA_CLIENT_WLAN2_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 18,
+			.channel_id	= 9,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_WLAN3_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 19,
+			.channel_id	= 10,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_USB_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 17,
+			.channel_id	= 8,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_USB_DPL_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 11,
+			.channel_id	= 2,
+			.tlv_count	= 4,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_LAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 9,
+			.channel_id	= 5,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_WAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 10,
+			.channel_id	= 6,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_Q6_LAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 13,
+			.channel_id	= 3,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_Q6_WAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 12,
+			.channel_id	= 2,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	/* Only for testing */
+	[IPA_CLIENT_TEST2_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 18,
+			.channel_id	= 9,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST3_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 19,
+			.channel_id	= 10,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST4_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 11,
+			.channel_id	= 2,
+			.tlv_count	= 4,
+			.ee		= IPA_EE_AP,
+		},
+	},
+/* Dummy consumer (endpoint 31) is used in L2TP rt rule */
+	[IPA_CLIENT_DUMMY_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 31,
+			.channel_id	= 31,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+};
+
+/** ipa_client_ep_id() - provide endpoint mapping
+ * @client: client type
+ *
+ * Return value: endpoint mapping
+ */
+u32 ipa_client_ep_id(enum ipa_client_type client)
+{
+	return ipa_ep_configuration[client].ipa_gsi_ep_info.ep_id;
+}
+
+u32 ipa_client_channel_id(enum ipa_client_type client)
+{
+	return ipa_ep_configuration[client].ipa_gsi_ep_info.channel_id;
+}
+
+u32 ipa_client_tlv_count(enum ipa_client_type client)
+{
+	return ipa_ep_configuration[client].ipa_gsi_ep_info.tlv_count;
+}
+
+enum ipa_seq_type ipa_endp_seq_type(u32 ep_id)
+{
+	return ipa_ep_configuration[ipa_ctx->ep[ep_id].client].seq_type;
+}
+
+/** ipa_sram_settings_read() - Read SRAM settings from HW
+ *
+ * Returns:	None
+ */
+void ipa_sram_settings_read(void)
+{
+	struct ipa_reg_shared_mem_size mem_size;
+
+	ipa_read_reg_fields(IPA_SHARED_MEM_SIZE, &mem_size);
+
+	/* reg fields are in 8B units */
+	ipa_ctx->smem_offset = mem_size.shared_mem_baddr * 8;
+	ipa_ctx->smem_size = mem_size.shared_mem_size * 8;
+
+	ipa_debug("sram size 0x%x offset 0x%x\n", ipa_ctx->smem_size,
+		  ipa_ctx->smem_offset);
+}
+
+/** ipa_init_hw() - initialize HW */
+void ipa_init_hw(void)
+{
+	struct ipa_reg_qsb_max_writes max_writes;
+	struct ipa_reg_qsb_max_reads max_reads;
+
+	/* SDM845 has IPA version 3.5.1 */
+	ipa_write_reg(IPA_BCR, IPA_BCR_REG_VAL);
+
+	ipa_reg_qsb_max_writes(&max_writes, 8, 4);
+	ipa_write_reg_fields(IPA_QSB_MAX_WRITES, &max_writes);
+
+	ipa_reg_qsb_max_reads(&max_reads, 8, 12);
+	ipa_write_reg_fields(IPA_QSB_MAX_READS, &max_reads);
+}
+
+/** ipa_filter_bitmap_init() - Initialize the bitmap
+ * that represents the End-points that supports filtering
+ */
+u32 ipa_filter_bitmap_init(void)
+{
+	enum ipa_client_type client;
+	u32 filter_bitmap = 0;
+	u32 count = 0;
+
+	for (client = 0; client < IPA_CLIENT_MAX ; client++) {
+		const struct ipa_ep_configuration *ep_config;
+
+		ep_config = &ipa_ep_configuration[client];
+		if (!ep_config->support_flt)
+			continue;
+		if (++count > IPA_MEM_FLT_COUNT)
+			return 0;	/* Too many filtering endpoints */
+
+		filter_bitmap |= BIT(ep_config->ipa_gsi_ep_info.ep_id);
+	}
+
+	return filter_bitmap;
+}
+
+/* In IPAv3 only endpoints 0-3 can be configured to deaggregation */
+bool ipa_endp_aggr_support(u32 ep_id)
+{
+	return ep_id < 4;
+}
+
+/** ipa_endp_init_hdr_write()
+ *
+ * @ep_id:	endpoint whose header config register should be written
+ */
+static void ipa_endp_init_hdr_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_HDR_N, ep_id, &ep->init_hdr);
+}
+
+/** ipa_endp_init_hdr_ext_write() - write endpoint extended header register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void
+ipa_endp_init_hdr_ext_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_HDR_EXT_N, ep_id,
+			       &ep->hdr_ext);
+}
+
+/** ipa_endp_init_aggr_write() write endpoint aggregation register
+ *
+ * @ep_id:	endpoint whose aggregation config register should be written
+ */
+static void ipa_endp_init_aggr_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_AGGR_N, ep_id, &ep->init_aggr);
+}
+
+/** ipa_endp_init_cfg_write() - write endpoint configuration register
+ *
+ * @ep_id:	endpoint whose configuration register should be written
+ */
+static void ipa_endp_init_cfg_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_CFG_N, ep_id, &ep->init_cfg);
+}
+
+/** ipa_endp_init_mode_write() - write endpoint mode register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_init_mode_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_MODE_N, ep_id,
+			       &ep->init_mode);
+}
+
+/** ipa_endp_init_seq_write() - write endpoint sequencer register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_init_seq_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_SEQ_N, ep_id, &ep->init_seq);
+}
+
+/** ipa_endp_init_deaggr_write() - write endpoint deaggregation register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+void ipa_endp_init_deaggr_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_DEAGGR_N, ep_id,
+			       &ep->init_deaggr);
+}
+
+/** ipa_endp_init_hdr_metadata_mask_write() - endpoint metadata mask register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_init_hdr_metadata_mask_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_HDR_METADATA_MASK_N, ep_id,
+			       &ep->metadata_mask);
+}
+
+/** ipa_endp_init_hdr_metadata_mask_write() - endpoint metadata mask register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_status_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_STATUS_N, ep_id, &ep->status);
+}
+
+/** ipa_cfg_ep - IPA end-point configuration
+ * @ep_id:	[in] endpoint id assigned by IPA to client
+ * @dst:	[in] destination client handle (ignored for consumer clients)
+ *
+ * This includes nat, IPv6CT, header, mode, aggregation and route settings and
+ * is a one shot API to configure the IPA end-point fully
+ *
+ * Returns:	0 on success, negative on failure
+ *
+ * Note:	Should not be called from atomic context
+ */
+void ipa_cfg_ep(u32 ep_id)
+{
+	ipa_endp_init_hdr_write(ep_id);
+	ipa_endp_init_hdr_ext_write(ep_id);
+
+	ipa_endp_init_aggr_write(ep_id);
+	ipa_endp_init_cfg_write(ep_id);
+
+	if (ipa_producer(ipa_ctx->ep[ep_id].client)) {
+		ipa_endp_init_mode_write(ep_id);
+		ipa_endp_init_seq_write(ep_id);
+		ipa_endp_init_deaggr_write(ep_id);
+	} else {
+		ipa_endp_init_hdr_metadata_mask_write(ep_id);
+	}
+
+	ipa_endp_status_write(ep_id);
+}
+
+int ipa_interconnect_init(struct device *dev)
+{
+	struct icc_path *path;
+
+	path = of_icc_get(dev, "memory");
+	if (IS_ERR(path))
+		goto err_return;
+	ipa_ctx->memory_path = path;
+
+	path = of_icc_get(dev, "imem");
+	if (IS_ERR(path))
+		goto err_memory_path_put;
+	ipa_ctx->imem_path = path;
+
+	path = of_icc_get(dev, "config");
+	if (IS_ERR(path))
+		goto err_imem_path_put;
+	ipa_ctx->config_path = path;
+
+	return 0;
+
+err_imem_path_put:
+	icc_put(ipa_ctx->imem_path);
+	ipa_ctx->imem_path = NULL;
+err_memory_path_put:
+	icc_put(ipa_ctx->memory_path);
+	ipa_ctx->memory_path = NULL;
+err_return:
+
+	return PTR_ERR(path);
+}
+
+void ipa_interconnect_exit(void)
+{
+	icc_put(ipa_ctx->config_path);
+	ipa_ctx->config_path = NULL;
+
+	icc_put(ipa_ctx->imem_path);
+	ipa_ctx->imem_path = NULL;
+
+	icc_put(ipa_ctx->memory_path);
+	ipa_ctx->memory_path = NULL;
+}
+
+/* Currently we only use bandwidth level, so just "enable" interconnects */
+int ipa_interconnect_enable(void)
+{
+	int ret;
+
+	ret = icc_set(ipa_ctx->memory_path, IPA_MEMORY_AVG, IPA_MEMORY_PEAK);
+	if (ret)
+		return ret;
+
+	ret = icc_set(ipa_ctx->imem_path, IPA_IMEM_AVG, IPA_IMEM_PEAK);
+	if (ret)
+		goto err_disable_memory_path;
+
+	ret = icc_set(ipa_ctx->config_path, IPA_CONFIG_AVG, IPA_CONFIG_PEAK);
+	if (!ret)
+		return 0;	/* Success */
+
+	(void)icc_set(ipa_ctx->imem_path, 0, 0);
+err_disable_memory_path:
+	(void)icc_set(ipa_ctx->memory_path, 0, 0);
+
+	return ret;
+}
+
+/* To disable an interconnect, we just its bandwidth to 0 */
+int ipa_interconnect_disable(void)
+{
+	int ret;
+
+	ret = icc_set(ipa_ctx->memory_path, 0, 0);
+	if (ret)
+		return ret;
+
+	ret = icc_set(ipa_ctx->imem_path, 0, 0);
+	if (ret)
+		goto err_reenable_memory_path;
+
+	ret = icc_set(ipa_ctx->config_path, 0, 0);
+	if (!ret)
+		return 0;	/* Success */
+
+	/* Re-enable things in the event of an error */
+	(void)icc_set(ipa_ctx->imem_path, IPA_IMEM_AVG, IPA_IMEM_PEAK);
+err_reenable_memory_path:
+	(void)icc_set(ipa_ctx->memory_path, IPA_MEMORY_AVG, IPA_MEMORY_PEAK);
+
+	return ret;
+}
+
+/** ipa_proxy_clk_unvote() - called to remove IPA clock proxy vote
+ *
+ * Return value: none
+ */
+void ipa_proxy_clk_unvote(void)
+{
+	if (ipa_ctx->modem_clk_vote_valid) {
+		ipa_client_remove();
+		ipa_ctx->modem_clk_vote_valid = false;
+	}
+}
+
+/** ipa_proxy_clk_vote() - called to add IPA clock proxy vote
+ *
+ * Return value: none
+ */
+void ipa_proxy_clk_vote(void)
+{
+	if (!ipa_ctx->modem_clk_vote_valid) {
+		ipa_client_add();
+		ipa_ctx->modem_clk_vote_valid = true;
+	}
+}
+
+u32 ipa_get_ep_count(void)
+{
+	return ipa_read_reg(IPA_ENABLED_PIPES);
+}
+
+/** ipa_is_modem_ep()- Checks if endpoint is owned by the modem
+ *
+ * @ep_id: endpoint identifier
+ * Return value: true if owned by modem, false otherwize
+ */
+bool ipa_is_modem_ep(u32 ep_id)
+{
+	int client_idx;
+
+	for (client_idx = 0; client_idx < IPA_CLIENT_MAX; client_idx++) {
+		if (!ipa_modem_consumer(client_idx) &&
+		    !ipa_modem_producer(client_idx))
+			continue;
+		if (ipa_client_ep_id(client_idx) == ep_id)
+			return true;
+	}
+
+	return false;
+}
+
+static void ipa_src_rsrc_grp_init(enum ipa_rsrc_grp_type_src n)
+{
+	struct ipa_reg_rsrc_grp_xy_rsrc_type_n limits;
+	const struct rsrc_min_max *x_limits;
+	const struct rsrc_min_max *y_limits;
+
+	x_limits = &ipa_src_rsrc_grp[n][IPA_RSRC_GROUP_LWA_DL];
+	y_limits = &ipa_src_rsrc_grp[n][IPA_RSRC_GROUP_UL_DL];
+	ipa_reg_rsrc_grp_xy_rsrc_type_n(&limits, x_limits->min, x_limits->max,
+				        y_limits->min, y_limits->max);
+
+	ipa_write_reg_n_fields(IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N, n, &limits);
+}
+
+static void ipa_dst_rsrc_grp_init(enum ipa_rsrc_grp_type_dst n)
+{
+	struct ipa_reg_rsrc_grp_xy_rsrc_type_n limits;
+	const struct rsrc_min_max *x_limits;
+	const struct rsrc_min_max *y_limits;
+
+	x_limits = &ipa_dst_rsrc_grp[n][IPA_RSRC_GROUP_LWA_DL];
+	y_limits = &ipa_dst_rsrc_grp[n][IPA_RSRC_GROUP_UL_DL];
+	ipa_reg_rsrc_grp_xy_rsrc_type_n(&limits, x_limits->min, x_limits->max,
+				        y_limits->min, y_limits->max);
+
+	ipa_write_reg_n_fields(IPA_DST_RSRC_GRP_01_RSRC_TYPE_N, n, &limits);
+}
+
+void ipa_set_resource_groups_min_max_limits(void)
+{
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_PKT_CONTEXTS);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRS_DESCRIPTOR_LISTS);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_DESCRIPTOR_BUFF);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_HPS_DMARS);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_ACK_ENTRIES);
+
+	ipa_dst_rsrc_grp_init(IPA_RSRC_GRP_TYPE_DST_DATA_SECTORS);
+	ipa_dst_rsrc_grp_init(IPA_RSRC_GRP_TYPE_DST_DPS_DMARS);
+}
+
+static void ipa_gsi_poll_after_suspend(struct ipa_ep_context *ep)
+{
+	ipa_rx_switch_to_poll_mode(ep->sys);
+}
+
+/* Suspend a consumer endpoint */
+static void ipa_ep_cons_suspend(enum ipa_client_type client)
+{
+	struct ipa_reg_endp_init_ctrl init_ctrl;
+	u32 ep_id = ipa_client_ep_id(client);
+
+	ipa_reg_endp_init_ctrl(&init_ctrl, true);
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+
+	/* Due to a hardware bug, a client suspended with an open
+	 * aggregation frame will not generate a SUSPEND IPA interrupt.
+	 * We work around this by force-closing the aggregation frame,
+	 * then simulating the arrival of such an interrupt.
+	 */
+	ipa_suspend_active_aggr_wa(ep_id);
+
+	ipa_gsi_poll_after_suspend(&ipa_ctx->ep[ep_id]);
+}
+
+void ipa_ep_suspend_all(void)
+{
+	ipa_ep_cons_suspend(IPA_CLIENT_APPS_WAN_CONS);
+	ipa_ep_cons_suspend(IPA_CLIENT_APPS_LAN_CONS);
+}
+
+/* Resume a suspended consumer endpoint */
+static void ipa_ep_cons_resume(enum ipa_client_type client)
+{
+	struct ipa_reg_endp_init_ctrl init_ctrl;
+	struct ipa_ep_context *ep;
+	u32 ep_id;
+
+	ep_id = ipa_client_ep_id(client);
+	ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_ctrl(&init_ctrl, false);
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+
+	if (!ipa_ep_polling(ep))
+		gsi_channel_intr_enable(ipa_ctx->gsi, ep->channel_id);
+}
+
+void ipa_ep_resume_all(void)
+{
+	ipa_ep_cons_resume(IPA_CLIENT_APPS_LAN_CONS);
+	ipa_ep_cons_resume(IPA_CLIENT_APPS_WAN_CONS);
+}
+
+/** ipa_cfg_route() - configure IPA route
+ * @route: IPA route
+ *
+ * Return codes:
+ * 0: success
+ */
+void ipa_cfg_default_route(enum ipa_client_type client)
+{
+	struct ipa_reg_route route;
+
+	ipa_reg_route(&route, ipa_client_ep_id(client));
+	ipa_write_reg_fields(IPA_ROUTE, &route);
+}
+
+/* In certain cases we need to issue a command to reliably clear the
+ * IPA pipeline.  Sending a 1-byte DMA task is sufficient, and this
+ * function preallocates a command to do just that.  There are
+ * conditions (process context in KILL state) where DMA allocations
+ * can fail, and we need to be able to issue this command to put the
+ * hardware in a known state.  By preallocating the command here we
+ * guarantee it can't fail for that reason.
+ */
+int ipa_gsi_dma_task_alloc(void)
+{
+	struct ipa_dma_mem *mem = &ipa_ctx->dma_task_info.mem;
+
+	if (ipa_dma_alloc(mem, IPA_GSI_CHANNEL_STOP_PKT_SIZE, GFP_KERNEL))
+		return -ENOMEM;
+
+	/* IPA_IMM_CMD_DMA_TASK_32B_ADDR */
+	ipa_ctx->dma_task_info.payload = ipahal_dma_task_32b_addr_pyld(mem);
+	if (!ipa_ctx->dma_task_info.payload) {
+		ipa_dma_free(mem);
+
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void ipa_gsi_dma_task_free(void)
+{
+	struct ipa_dma_mem *mem = &ipa_ctx->dma_task_info.mem;
+
+	ipahal_payload_free(ipa_ctx->dma_task_info.payload);
+	ipa_ctx->dma_task_info.payload = NULL;
+	ipa_dma_free(mem);
+}
+
+/** ipa_gsi_dma_task_inject()- Send DMA_TASK to IPA for GSI stop channel
+ *
+ * Send a DMA_TASK of 1B to IPA to unblock GSI channel in STOP_IN_PROG.
+ * Return value: 0 on success, negative otherwise
+ */
+static int ipa_gsi_dma_task_inject(void)
+{
+	struct ipa_desc desc = { };
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_DMA_TASK_32B_ADDR;
+	desc.payload = ipa_ctx->dma_task_info.payload;
+
+	return ipa_send_cmd_timeout(&desc, IPA_GSI_DMA_TASK_TIMEOUT);
+}
+
+/** ipa_stop_gsi_channel()- Stops a GSI channel in IPA
+ * @chan_hdl: GSI channel handle
+ *
+ * This function implements the sequence to stop a GSI channel
+ * in IPA. This function returns when the channel is is STOP state.
+ *
+ * Return value: 0 on success, negative otherwise
+ */
+int ipa_stop_gsi_channel(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	int ret;
+	int i;
+
+	if (ipa_producer(ep->client))
+		return gsi_channel_stop(ipa_ctx->gsi, ep->channel_id);
+
+	for (i = 0; i < IPA_GSI_CHANNEL_STOP_MAX_RETRY; i++) {
+		ret = gsi_channel_stop(ipa_ctx->gsi, ep->channel_id);
+		if (ret != -EAGAIN && ret != -ETIMEDOUT)
+			return ret;
+
+		/* Send a 1B packet DMA_TASK to IPA and try again */
+		ret = ipa_gsi_dma_task_inject();
+		if (ret)
+			return ret;
+
+		/* sleep for short period to flush IPA */
+		usleep_range(IPA_GSI_CHANNEL_STOP_SLEEP_MIN,
+			     IPA_GSI_CHANNEL_STOP_SLEEP_MAX);
+	}
+
+	ipa_err("Failed	 to stop GSI channel with retries\n");
+
+	return -EFAULT;
+}
+
+/** ipa_enable_dcd() - enable dynamic clock division on IPA
+ *
+ * Return value: Non applicable
+ *
+ */
+void ipa_enable_dcd(void)
+{
+	struct ipa_reg_idle_indication_cfg indication;
+
+	/* recommended values for IPA 3.5 according to IPA HPG */
+	ipa_reg_idle_indication_cfg(&indication, 256, 0);
+	ipa_write_reg_fields(IPA_IDLE_INDICATION_CFG, &indication);
+}
+
+/** ipa_set_flt_tuple_mask() - Sets the flt tuple masking for the given
+ * endpoint.  Endpoint must be for AP (not modem) and support filtering.
+ * Updates the the filtering masking values without changing the rt ones.
+ *
+ * @ep_id: filter endpoint to configure the tuple masking
+ * @tuple: the tuple members masking
+ * Returns:	0 on success, negative on failure
+ *
+ */
+void ipa_set_flt_tuple_mask(u32 ep_id)
+{
+	struct ipa_ep_filter_router_hsh_cfg hsh_cfg;
+
+	ipa_read_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, ep_id,
+			      &hsh_cfg);
+
+	ipa_reg_hash_tuple(&hsh_cfg.flt);
+
+	ipa_write_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, ep_id,
+			       &hsh_cfg);
+}
+
+/** ipa_set_rt_tuple_mask() - Sets the rt tuple masking for the given tbl
+ *  table index must be for AP EP (not modem)
+ *  updates the the routing masking values without changing the flt ones.
+ *
+ * @tbl_idx: routing table index to configure the tuple masking
+ * @tuple: the tuple members masking
+ * Returns:	 0 on success, negative on failure
+ *
+ */
+void ipa_set_rt_tuple_mask(int tbl_idx)
+{
+	struct ipa_ep_filter_router_hsh_cfg hsh_cfg;
+
+	ipa_read_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, tbl_idx,
+			      &hsh_cfg);
+
+	ipa_reg_hash_tuple(&hsh_cfg.rt);
+
+	ipa_write_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, tbl_idx,
+			       &hsh_cfg);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("IPA HW device driver");
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 08/12] soc: qcom: ipa: utility functions
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch contains "ipa_utils.c", which provides some miscellaneous
supporting code.  Included are:
  - Endpoint configuration.  The IPA hardware has a fixed number of
    endpoints (pipes) available.  In some cases the AP and modem
    must agree on aspects of these.  A key example is that they need
    to agree what particular endpoints are used for (the modem can't
    send messages to the AP command endpoint, for example).  There
    is a static array that defines these parameters, and this is
    found in "ipa_utils.c".
  - Resource group configuration.  The IPA has a number of internal
    resources it uses for its operation.  These are configurable,
    and it is up to the AP to set these configuration values at
    initialization time.  There are some static arrays that define
    these configuration values.  Functions are also defined here
    to send those values to hardware.
  - Shared memory.  The IPA uses a region of shared memory to hold
    various data structures.  A function ipa_sram_settings_read()
    fetches the location and size of this shared memory.  (The
    individual regions are currently initialized in "ipa_main.c".)
  - Endpoint configuration.  Each endpoint (or channel) has a number
    of configurable properties.  Functions found in this file are
    used to configure these properties.
  - Interconnect handling.  The IPA driver depends on the
    interconnect framework to request that buses it uses be enabled
    when needed.

This is not a complete list, but it covers much of the functionality
found in "ipa_utils.c".

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_utils.c | 1035 +++++++++++++++++++++++++++++++++++
 1 file changed, 1035 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_utils.c

diff --git a/drivers/net/ipa/ipa_utils.c b/drivers/net/ipa/ipa_utils.c
new file mode 100644
index 000000000000..085b0218779b
--- /dev/null
+++ b/drivers/net/ipa/ipa_utils.c
@@ -0,0 +1,1035 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "ipa_dma.h"
+#include "ipa_i.h"
+#include "ipahal.h"
+
+/* Interconnect path bandwidths (each times 1000 bytes per second) */
+#define IPA_MEMORY_AVG			80000
+#define IPA_MEMORY_PEAK			600000
+
+#define IPA_IMEM_AVG			80000
+#define IPA_IMEM_PEAK			350000
+
+#define IPA_CONFIG_AVG			40000
+#define IPA_CONFIG_PEAK			40000
+
+#define IPA_BCR_REG_VAL			0x0000003b
+
+#define IPA_GSI_DMA_TASK_TIMEOUT	15	/* milliseconds */
+
+#define IPA_GSI_CHANNEL_STOP_SLEEP_MIN	1000	/* microseconds */
+#define IPA_GSI_CHANNEL_STOP_SLEEP_MAX	2000	/* microseconds */
+
+#define QMB_MASTER_SELECT_DDR		0
+
+enum ipa_rsrc_group {
+	IPA_RSRC_GROUP_LWA_DL,	/* currently not used */
+	IPA_RSRC_GROUP_UL_DL,
+	IPA_RSRC_GROUP_MAX,
+};
+
+enum ipa_rsrc_grp_type_src {
+	IPA_RSRC_GRP_TYPE_SRC_PKT_CONTEXTS,
+	IPA_RSRC_GRP_TYPE_SRS_DESCRIPTOR_LISTS,
+	IPA_RSRC_GRP_TYPE_SRC_DESCRIPTOR_BUFF,
+	IPA_RSRC_GRP_TYPE_SRC_HPS_DMARS,
+	IPA_RSRC_GRP_TYPE_SRC_ACK_ENTRIES,
+};
+
+enum ipa_rsrc_grp_type_dst {
+	IPA_RSRC_GRP_TYPE_DST_DATA_SECTORS,
+	IPA_RSRC_GRP_TYPE_DST_DPS_DMARS,
+};
+
+enum ipa_rsrc_grp_type_rx {
+	IPA_RSRC_GRP_TYPE_RX_HPS_CMDQ,
+	IPA_RSRC_GRP_TYPE_RX_MAX
+};
+
+struct rsrc_min_max {
+	u32 min;
+	u32 max;
+};
+
+/* IPA_HW_v3_5_1 */
+static const struct rsrc_min_max ipa_src_rsrc_grp[][IPA_RSRC_GROUP_MAX] = {
+	[IPA_RSRC_GRP_TYPE_SRC_PKT_CONTEXTS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 1,	.max = 63, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 1,	.max = 63, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRS_DESCRIPTOR_LISTS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 10,	.max = 10, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 10,	.max = 10, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRC_DESCRIPTOR_BUFF] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 12,	.max = 12, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 14,	.max = 14, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRC_HPS_DMARS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 0,	.max = 63, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 0,	.max = 63, },
+	},
+	[IPA_RSRC_GRP_TYPE_SRC_ACK_ENTRIES] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 14,	.max = 14, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 20,	.max = 20, },
+	},
+};
+
+/* IPA_HW_v3_5_1 */
+static const struct rsrc_min_max ipa_dst_rsrc_grp[][IPA_RSRC_GROUP_MAX] = {
+	[IPA_RSRC_GRP_TYPE_DST_DATA_SECTORS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 4,	.max = 4, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 4,	.max = 4, },
+	},
+	[IPA_RSRC_GRP_TYPE_DST_DPS_DMARS] = {
+		[IPA_RSRC_GROUP_LWA_DL]	= { .min = 2,	.max = 63, },
+		[IPA_RSRC_GROUP_UL_DL]	= { .min = 1,	.max = 63, },
+	},
+};
+
+/**
+ * struct ipa_gsi_ep_config - GSI endpoint configuration.
+ * @ep_id:	IPA endpoint identifier.
+ * @channel_id:	GSI channel number used for this endpoint.
+ * @tlv_count:	The number of TLV (type-length-value) entries for the channel.
+ * @ee:		Execution environment endpoint is associated with.
+ *
+ * Each GSI endpoint has a set of configuration parameters defined within
+ * entries in the ipa_ep_configuration[] array.  Its @ep_id field uniquely
+ * defines the endpoint, and @channel_id defines which data channel (ring
+ * buffer) is used for the endpoint.
+ * XXX TLV
+ * XXX ee is never used in the code
+ */
+struct ipa_gsi_ep_config {
+	u32 ep_id;
+	u32 channel_id;
+	u32 tlv_count;
+	u32 ee;
+};
+
+struct ipa_ep_configuration {
+	bool support_flt;
+	enum ipa_seq_type seq_type;
+	struct ipa_gsi_ep_config ipa_gsi_ep_info;
+};
+
+/* IPA_HW_v3_5_1 */
+/* clients not included in the list below are considered as invalid */
+static const struct ipa_ep_configuration ipa_ep_configuration[] = {
+	[IPA_CLIENT_WLAN1_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 7,
+			.channel_id	= 1,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	[IPA_CLIENT_USB_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 0,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_LAN_PROD] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 8,
+			.channel_id	= 7,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_WAN_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 2,
+			.channel_id	= 3,
+			.tlv_count	= 16,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_CMD_PROD] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_DMA_ONLY,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 5,
+			.channel_id	= 4,
+			.tlv_count	= 20,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_Q6_LAN_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 3,
+			.channel_id	= 0,
+			.tlv_count	= 16,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_Q6_WAN_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 6,
+			.channel_id	= 4,
+			.tlv_count	= 12,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_Q6_CMD_PROD] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_PKT_PROCESS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 4,
+			.channel_id	= 1,
+			.tlv_count	= 20,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_TEST_CONS] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 14,
+			.channel_id	= 5,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_TEST1_CONS] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 15,
+			.channel_id	= 2,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	/* Only for testing */
+	[IPA_CLIENT_TEST_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 0,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST1_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 0,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST2_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 2,
+			.channel_id	= 3,
+			.tlv_count	= 16,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST3_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 4,
+			.channel_id	= 1,
+			.tlv_count	= 20,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_TEST4_PROD] = {
+		.support_flt	= true,
+		.seq_type	= IPA_SEQ_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 1,
+			.channel_id	= 0,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	[IPA_CLIENT_WLAN1_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 16,
+			.channel_id	= 3,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_UC,
+		},
+	},
+	[IPA_CLIENT_WLAN2_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 18,
+			.channel_id	= 9,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_WLAN3_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 19,
+			.channel_id	= 10,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_USB_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 17,
+			.channel_id	= 8,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_USB_DPL_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 11,
+			.channel_id	= 2,
+			.tlv_count	= 4,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_LAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 9,
+			.channel_id	= 5,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_APPS_WAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 10,
+			.channel_id	= 6,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_Q6_LAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 13,
+			.channel_id	= 3,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	[IPA_CLIENT_Q6_WAN_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 12,
+			.channel_id	= 2,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_Q6,
+		},
+	},
+	/* Only for testing */
+	[IPA_CLIENT_TEST2_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 18,
+			.channel_id	= 9,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST3_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 19,
+			.channel_id	= 10,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+	[IPA_CLIENT_TEST4_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 11,
+			.channel_id	= 2,
+			.tlv_count	= 4,
+			.ee		= IPA_EE_AP,
+		},
+	},
+/* Dummy consumer (endpoint 31) is used in L2TP rt rule */
+	[IPA_CLIENT_DUMMY_CONS] = {
+		.support_flt	= false,
+		.seq_type	= IPA_SEQ_INVALID,
+		.ipa_gsi_ep_info = {
+			.ep_id		= 31,
+			.channel_id	= 31,
+			.tlv_count	= 8,
+			.ee		= IPA_EE_AP,
+		},
+	},
+};
+
+/** ipa_client_ep_id() - provide endpoint mapping
+ * @client: client type
+ *
+ * Return value: endpoint mapping
+ */
+u32 ipa_client_ep_id(enum ipa_client_type client)
+{
+	return ipa_ep_configuration[client].ipa_gsi_ep_info.ep_id;
+}
+
+u32 ipa_client_channel_id(enum ipa_client_type client)
+{
+	return ipa_ep_configuration[client].ipa_gsi_ep_info.channel_id;
+}
+
+u32 ipa_client_tlv_count(enum ipa_client_type client)
+{
+	return ipa_ep_configuration[client].ipa_gsi_ep_info.tlv_count;
+}
+
+enum ipa_seq_type ipa_endp_seq_type(u32 ep_id)
+{
+	return ipa_ep_configuration[ipa_ctx->ep[ep_id].client].seq_type;
+}
+
+/** ipa_sram_settings_read() - Read SRAM settings from HW
+ *
+ * Returns:	None
+ */
+void ipa_sram_settings_read(void)
+{
+	struct ipa_reg_shared_mem_size mem_size;
+
+	ipa_read_reg_fields(IPA_SHARED_MEM_SIZE, &mem_size);
+
+	/* reg fields are in 8B units */
+	ipa_ctx->smem_offset = mem_size.shared_mem_baddr * 8;
+	ipa_ctx->smem_size = mem_size.shared_mem_size * 8;
+
+	ipa_debug("sram size 0x%x offset 0x%x\n", ipa_ctx->smem_size,
+		  ipa_ctx->smem_offset);
+}
+
+/** ipa_init_hw() - initialize HW */
+void ipa_init_hw(void)
+{
+	struct ipa_reg_qsb_max_writes max_writes;
+	struct ipa_reg_qsb_max_reads max_reads;
+
+	/* SDM845 has IPA version 3.5.1 */
+	ipa_write_reg(IPA_BCR, IPA_BCR_REG_VAL);
+
+	ipa_reg_qsb_max_writes(&max_writes, 8, 4);
+	ipa_write_reg_fields(IPA_QSB_MAX_WRITES, &max_writes);
+
+	ipa_reg_qsb_max_reads(&max_reads, 8, 12);
+	ipa_write_reg_fields(IPA_QSB_MAX_READS, &max_reads);
+}
+
+/** ipa_filter_bitmap_init() - Initialize the bitmap
+ * that represents the End-points that supports filtering
+ */
+u32 ipa_filter_bitmap_init(void)
+{
+	enum ipa_client_type client;
+	u32 filter_bitmap = 0;
+	u32 count = 0;
+
+	for (client = 0; client < IPA_CLIENT_MAX ; client++) {
+		const struct ipa_ep_configuration *ep_config;
+
+		ep_config = &ipa_ep_configuration[client];
+		if (!ep_config->support_flt)
+			continue;
+		if (++count > IPA_MEM_FLT_COUNT)
+			return 0;	/* Too many filtering endpoints */
+
+		filter_bitmap |= BIT(ep_config->ipa_gsi_ep_info.ep_id);
+	}
+
+	return filter_bitmap;
+}
+
+/* In IPAv3 only endpoints 0-3 can be configured to deaggregation */
+bool ipa_endp_aggr_support(u32 ep_id)
+{
+	return ep_id < 4;
+}
+
+/** ipa_endp_init_hdr_write()
+ *
+ * @ep_id:	endpoint whose header config register should be written
+ */
+static void ipa_endp_init_hdr_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_HDR_N, ep_id, &ep->init_hdr);
+}
+
+/** ipa_endp_init_hdr_ext_write() - write endpoint extended header register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void
+ipa_endp_init_hdr_ext_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_HDR_EXT_N, ep_id,
+			       &ep->hdr_ext);
+}
+
+/** ipa_endp_init_aggr_write() write endpoint aggregation register
+ *
+ * @ep_id:	endpoint whose aggregation config register should be written
+ */
+static void ipa_endp_init_aggr_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_AGGR_N, ep_id, &ep->init_aggr);
+}
+
+/** ipa_endp_init_cfg_write() - write endpoint configuration register
+ *
+ * @ep_id:	endpoint whose configuration register should be written
+ */
+static void ipa_endp_init_cfg_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_CFG_N, ep_id, &ep->init_cfg);
+}
+
+/** ipa_endp_init_mode_write() - write endpoint mode register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_init_mode_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_MODE_N, ep_id,
+			       &ep->init_mode);
+}
+
+/** ipa_endp_init_seq_write() - write endpoint sequencer register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_init_seq_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_SEQ_N, ep_id, &ep->init_seq);
+}
+
+/** ipa_endp_init_deaggr_write() - write endpoint deaggregation register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+void ipa_endp_init_deaggr_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_DEAGGR_N, ep_id,
+			       &ep->init_deaggr);
+}
+
+/** ipa_endp_init_hdr_metadata_mask_write() - endpoint metadata mask register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_init_hdr_metadata_mask_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_HDR_METADATA_MASK_N, ep_id,
+			       &ep->metadata_mask);
+}
+
+/** ipa_endp_init_hdr_metadata_mask_write() - endpoint metadata mask register
+ *
+ * @ep_id:	endpoint whose register should be written
+ */
+static void ipa_endp_status_write(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_write_reg_n_fields(IPA_ENDP_STATUS_N, ep_id, &ep->status);
+}
+
+/** ipa_cfg_ep - IPA end-point configuration
+ * @ep_id:	[in] endpoint id assigned by IPA to client
+ * @dst:	[in] destination client handle (ignored for consumer clients)
+ *
+ * This includes nat, IPv6CT, header, mode, aggregation and route settings and
+ * is a one shot API to configure the IPA end-point fully
+ *
+ * Returns:	0 on success, negative on failure
+ *
+ * Note:	Should not be called from atomic context
+ */
+void ipa_cfg_ep(u32 ep_id)
+{
+	ipa_endp_init_hdr_write(ep_id);
+	ipa_endp_init_hdr_ext_write(ep_id);
+
+	ipa_endp_init_aggr_write(ep_id);
+	ipa_endp_init_cfg_write(ep_id);
+
+	if (ipa_producer(ipa_ctx->ep[ep_id].client)) {
+		ipa_endp_init_mode_write(ep_id);
+		ipa_endp_init_seq_write(ep_id);
+		ipa_endp_init_deaggr_write(ep_id);
+	} else {
+		ipa_endp_init_hdr_metadata_mask_write(ep_id);
+	}
+
+	ipa_endp_status_write(ep_id);
+}
+
+int ipa_interconnect_init(struct device *dev)
+{
+	struct icc_path *path;
+
+	path = of_icc_get(dev, "memory");
+	if (IS_ERR(path))
+		goto err_return;
+	ipa_ctx->memory_path = path;
+
+	path = of_icc_get(dev, "imem");
+	if (IS_ERR(path))
+		goto err_memory_path_put;
+	ipa_ctx->imem_path = path;
+
+	path = of_icc_get(dev, "config");
+	if (IS_ERR(path))
+		goto err_imem_path_put;
+	ipa_ctx->config_path = path;
+
+	return 0;
+
+err_imem_path_put:
+	icc_put(ipa_ctx->imem_path);
+	ipa_ctx->imem_path = NULL;
+err_memory_path_put:
+	icc_put(ipa_ctx->memory_path);
+	ipa_ctx->memory_path = NULL;
+err_return:
+
+	return PTR_ERR(path);
+}
+
+void ipa_interconnect_exit(void)
+{
+	icc_put(ipa_ctx->config_path);
+	ipa_ctx->config_path = NULL;
+
+	icc_put(ipa_ctx->imem_path);
+	ipa_ctx->imem_path = NULL;
+
+	icc_put(ipa_ctx->memory_path);
+	ipa_ctx->memory_path = NULL;
+}
+
+/* Currently we only use bandwidth level, so just "enable" interconnects */
+int ipa_interconnect_enable(void)
+{
+	int ret;
+
+	ret = icc_set(ipa_ctx->memory_path, IPA_MEMORY_AVG, IPA_MEMORY_PEAK);
+	if (ret)
+		return ret;
+
+	ret = icc_set(ipa_ctx->imem_path, IPA_IMEM_AVG, IPA_IMEM_PEAK);
+	if (ret)
+		goto err_disable_memory_path;
+
+	ret = icc_set(ipa_ctx->config_path, IPA_CONFIG_AVG, IPA_CONFIG_PEAK);
+	if (!ret)
+		return 0;	/* Success */
+
+	(void)icc_set(ipa_ctx->imem_path, 0, 0);
+err_disable_memory_path:
+	(void)icc_set(ipa_ctx->memory_path, 0, 0);
+
+	return ret;
+}
+
+/* To disable an interconnect, we just its bandwidth to 0 */
+int ipa_interconnect_disable(void)
+{
+	int ret;
+
+	ret = icc_set(ipa_ctx->memory_path, 0, 0);
+	if (ret)
+		return ret;
+
+	ret = icc_set(ipa_ctx->imem_path, 0, 0);
+	if (ret)
+		goto err_reenable_memory_path;
+
+	ret = icc_set(ipa_ctx->config_path, 0, 0);
+	if (!ret)
+		return 0;	/* Success */
+
+	/* Re-enable things in the event of an error */
+	(void)icc_set(ipa_ctx->imem_path, IPA_IMEM_AVG, IPA_IMEM_PEAK);
+err_reenable_memory_path:
+	(void)icc_set(ipa_ctx->memory_path, IPA_MEMORY_AVG, IPA_MEMORY_PEAK);
+
+	return ret;
+}
+
+/** ipa_proxy_clk_unvote() - called to remove IPA clock proxy vote
+ *
+ * Return value: none
+ */
+void ipa_proxy_clk_unvote(void)
+{
+	if (ipa_ctx->modem_clk_vote_valid) {
+		ipa_client_remove();
+		ipa_ctx->modem_clk_vote_valid = false;
+	}
+}
+
+/** ipa_proxy_clk_vote() - called to add IPA clock proxy vote
+ *
+ * Return value: none
+ */
+void ipa_proxy_clk_vote(void)
+{
+	if (!ipa_ctx->modem_clk_vote_valid) {
+		ipa_client_add();
+		ipa_ctx->modem_clk_vote_valid = true;
+	}
+}
+
+u32 ipa_get_ep_count(void)
+{
+	return ipa_read_reg(IPA_ENABLED_PIPES);
+}
+
+/** ipa_is_modem_ep()- Checks if endpoint is owned by the modem
+ *
+ * @ep_id: endpoint identifier
+ * Return value: true if owned by modem, false otherwize
+ */
+bool ipa_is_modem_ep(u32 ep_id)
+{
+	int client_idx;
+
+	for (client_idx = 0; client_idx < IPA_CLIENT_MAX; client_idx++) {
+		if (!ipa_modem_consumer(client_idx) &&
+		    !ipa_modem_producer(client_idx))
+			continue;
+		if (ipa_client_ep_id(client_idx) == ep_id)
+			return true;
+	}
+
+	return false;
+}
+
+static void ipa_src_rsrc_grp_init(enum ipa_rsrc_grp_type_src n)
+{
+	struct ipa_reg_rsrc_grp_xy_rsrc_type_n limits;
+	const struct rsrc_min_max *x_limits;
+	const struct rsrc_min_max *y_limits;
+
+	x_limits = &ipa_src_rsrc_grp[n][IPA_RSRC_GROUP_LWA_DL];
+	y_limits = &ipa_src_rsrc_grp[n][IPA_RSRC_GROUP_UL_DL];
+	ipa_reg_rsrc_grp_xy_rsrc_type_n(&limits, x_limits->min, x_limits->max,
+				        y_limits->min, y_limits->max);
+
+	ipa_write_reg_n_fields(IPA_SRC_RSRC_GRP_01_RSRC_TYPE_N, n, &limits);
+}
+
+static void ipa_dst_rsrc_grp_init(enum ipa_rsrc_grp_type_dst n)
+{
+	struct ipa_reg_rsrc_grp_xy_rsrc_type_n limits;
+	const struct rsrc_min_max *x_limits;
+	const struct rsrc_min_max *y_limits;
+
+	x_limits = &ipa_dst_rsrc_grp[n][IPA_RSRC_GROUP_LWA_DL];
+	y_limits = &ipa_dst_rsrc_grp[n][IPA_RSRC_GROUP_UL_DL];
+	ipa_reg_rsrc_grp_xy_rsrc_type_n(&limits, x_limits->min, x_limits->max,
+				        y_limits->min, y_limits->max);
+
+	ipa_write_reg_n_fields(IPA_DST_RSRC_GRP_01_RSRC_TYPE_N, n, &limits);
+}
+
+void ipa_set_resource_groups_min_max_limits(void)
+{
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_PKT_CONTEXTS);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRS_DESCRIPTOR_LISTS);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_DESCRIPTOR_BUFF);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_HPS_DMARS);
+	ipa_src_rsrc_grp_init(IPA_RSRC_GRP_TYPE_SRC_ACK_ENTRIES);
+
+	ipa_dst_rsrc_grp_init(IPA_RSRC_GRP_TYPE_DST_DATA_SECTORS);
+	ipa_dst_rsrc_grp_init(IPA_RSRC_GRP_TYPE_DST_DPS_DMARS);
+}
+
+static void ipa_gsi_poll_after_suspend(struct ipa_ep_context *ep)
+{
+	ipa_rx_switch_to_poll_mode(ep->sys);
+}
+
+/* Suspend a consumer endpoint */
+static void ipa_ep_cons_suspend(enum ipa_client_type client)
+{
+	struct ipa_reg_endp_init_ctrl init_ctrl;
+	u32 ep_id = ipa_client_ep_id(client);
+
+	ipa_reg_endp_init_ctrl(&init_ctrl, true);
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+
+	/* Due to a hardware bug, a client suspended with an open
+	 * aggregation frame will not generate a SUSPEND IPA interrupt.
+	 * We work around this by force-closing the aggregation frame,
+	 * then simulating the arrival of such an interrupt.
+	 */
+	ipa_suspend_active_aggr_wa(ep_id);
+
+	ipa_gsi_poll_after_suspend(&ipa_ctx->ep[ep_id]);
+}
+
+void ipa_ep_suspend_all(void)
+{
+	ipa_ep_cons_suspend(IPA_CLIENT_APPS_WAN_CONS);
+	ipa_ep_cons_suspend(IPA_CLIENT_APPS_LAN_CONS);
+}
+
+/* Resume a suspended consumer endpoint */
+static void ipa_ep_cons_resume(enum ipa_client_type client)
+{
+	struct ipa_reg_endp_init_ctrl init_ctrl;
+	struct ipa_ep_context *ep;
+	u32 ep_id;
+
+	ep_id = ipa_client_ep_id(client);
+	ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_ctrl(&init_ctrl, false);
+	ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+
+	if (!ipa_ep_polling(ep))
+		gsi_channel_intr_enable(ipa_ctx->gsi, ep->channel_id);
+}
+
+void ipa_ep_resume_all(void)
+{
+	ipa_ep_cons_resume(IPA_CLIENT_APPS_LAN_CONS);
+	ipa_ep_cons_resume(IPA_CLIENT_APPS_WAN_CONS);
+}
+
+/** ipa_cfg_route() - configure IPA route
+ * @route: IPA route
+ *
+ * Return codes:
+ * 0: success
+ */
+void ipa_cfg_default_route(enum ipa_client_type client)
+{
+	struct ipa_reg_route route;
+
+	ipa_reg_route(&route, ipa_client_ep_id(client));
+	ipa_write_reg_fields(IPA_ROUTE, &route);
+}
+
+/* In certain cases we need to issue a command to reliably clear the
+ * IPA pipeline.  Sending a 1-byte DMA task is sufficient, and this
+ * function preallocates a command to do just that.  There are
+ * conditions (process context in KILL state) where DMA allocations
+ * can fail, and we need to be able to issue this command to put the
+ * hardware in a known state.  By preallocating the command here we
+ * guarantee it can't fail for that reason.
+ */
+int ipa_gsi_dma_task_alloc(void)
+{
+	struct ipa_dma_mem *mem = &ipa_ctx->dma_task_info.mem;
+
+	if (ipa_dma_alloc(mem, IPA_GSI_CHANNEL_STOP_PKT_SIZE, GFP_KERNEL))
+		return -ENOMEM;
+
+	/* IPA_IMM_CMD_DMA_TASK_32B_ADDR */
+	ipa_ctx->dma_task_info.payload = ipahal_dma_task_32b_addr_pyld(mem);
+	if (!ipa_ctx->dma_task_info.payload) {
+		ipa_dma_free(mem);
+
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void ipa_gsi_dma_task_free(void)
+{
+	struct ipa_dma_mem *mem = &ipa_ctx->dma_task_info.mem;
+
+	ipahal_payload_free(ipa_ctx->dma_task_info.payload);
+	ipa_ctx->dma_task_info.payload = NULL;
+	ipa_dma_free(mem);
+}
+
+/** ipa_gsi_dma_task_inject()- Send DMA_TASK to IPA for GSI stop channel
+ *
+ * Send a DMA_TASK of 1B to IPA to unblock GSI channel in STOP_IN_PROG.
+ * Return value: 0 on success, negative otherwise
+ */
+static int ipa_gsi_dma_task_inject(void)
+{
+	struct ipa_desc desc = { };
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_DMA_TASK_32B_ADDR;
+	desc.payload = ipa_ctx->dma_task_info.payload;
+
+	return ipa_send_cmd_timeout(&desc, IPA_GSI_DMA_TASK_TIMEOUT);
+}
+
+/** ipa_stop_gsi_channel()- Stops a GSI channel in IPA
+ * @chan_hdl: GSI channel handle
+ *
+ * This function implements the sequence to stop a GSI channel
+ * in IPA. This function returns when the channel is is STOP state.
+ *
+ * Return value: 0 on success, negative otherwise
+ */
+int ipa_stop_gsi_channel(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	int ret;
+	int i;
+
+	if (ipa_producer(ep->client))
+		return gsi_channel_stop(ipa_ctx->gsi, ep->channel_id);
+
+	for (i = 0; i < IPA_GSI_CHANNEL_STOP_MAX_RETRY; i++) {
+		ret = gsi_channel_stop(ipa_ctx->gsi, ep->channel_id);
+		if (ret != -EAGAIN && ret != -ETIMEDOUT)
+			return ret;
+
+		/* Send a 1B packet DMA_TASK to IPA and try again */
+		ret = ipa_gsi_dma_task_inject();
+		if (ret)
+			return ret;
+
+		/* sleep for short period to flush IPA */
+		usleep_range(IPA_GSI_CHANNEL_STOP_SLEEP_MIN,
+			     IPA_GSI_CHANNEL_STOP_SLEEP_MAX);
+	}
+
+	ipa_err("Failed	 to stop GSI channel with retries\n");
+
+	return -EFAULT;
+}
+
+/** ipa_enable_dcd() - enable dynamic clock division on IPA
+ *
+ * Return value: Non applicable
+ *
+ */
+void ipa_enable_dcd(void)
+{
+	struct ipa_reg_idle_indication_cfg indication;
+
+	/* recommended values for IPA 3.5 according to IPA HPG */
+	ipa_reg_idle_indication_cfg(&indication, 256, 0);
+	ipa_write_reg_fields(IPA_IDLE_INDICATION_CFG, &indication);
+}
+
+/** ipa_set_flt_tuple_mask() - Sets the flt tuple masking for the given
+ * endpoint.  Endpoint must be for AP (not modem) and support filtering.
+ * Updates the the filtering masking values without changing the rt ones.
+ *
+ * @ep_id: filter endpoint to configure the tuple masking
+ * @tuple: the tuple members masking
+ * Returns:	0 on success, negative on failure
+ *
+ */
+void ipa_set_flt_tuple_mask(u32 ep_id)
+{
+	struct ipa_ep_filter_router_hsh_cfg hsh_cfg;
+
+	ipa_read_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, ep_id,
+			      &hsh_cfg);
+
+	ipa_reg_hash_tuple(&hsh_cfg.flt);
+
+	ipa_write_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, ep_id,
+			       &hsh_cfg);
+}
+
+/** ipa_set_rt_tuple_mask() - Sets the rt tuple masking for the given tbl
+ *  table index must be for AP EP (not modem)
+ *  updates the the routing masking values without changing the flt ones.
+ *
+ * @tbl_idx: routing table index to configure the tuple masking
+ * @tuple: the tuple members masking
+ * Returns:	 0 on success, negative on failure
+ *
+ */
+void ipa_set_rt_tuple_mask(int tbl_idx)
+{
+	struct ipa_ep_filter_router_hsh_cfg hsh_cfg;
+
+	ipa_read_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, tbl_idx,
+			      &hsh_cfg);
+
+	ipa_reg_hash_tuple(&hsh_cfg.rt);
+
+	ipa_write_reg_n_fields(IPA_ENDP_FILTER_ROUTER_HSH_CFG_N, tbl_idx,
+			       &hsh_cfg);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("IPA HW device driver");
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This patch includes "ipa_main.c", which consists mostly of the
initialization code.

The IPA is a hardware resource shared by multiple independent
execution environments (currently, the AP and the modem).  In some
cases, initialization must be performed by only one of these.  As an
example, the AP must initialize some filter table data structures
that are only used by the modem.  (And in general, some initialization
of IPA hardware is required regardless of whether it will be used.)

There are two phases of IPA initialization.  The first phase is
triggered by the probe of the driver.  It involves setting up
operating system resources, and doing some basic initialization
of IPA memory resources using register and DMA access.

The second phase involves configuration of enpoints used, and this
phase requires access to the GSI layer.  However the GSI layer is
requires some firmware to be loaded before it can be used.  So
the second stage (in ipa_post_init()) only occurs after it is known
firmware is loaded.

The GSI firmware can be loaded in two ways:  the modem can load it;
or Trust Zone code running on the AP can load it.  If the modem
loads the firmware, it will send an SMP2P interrupt to the AP to
signal that GSI firmware is loaded and the AP can proceed with its
second stage IPA initialization.  If Trust Zone is responsible for
loading the firmware, the IPA driver requests the firmware blob
from the file system and passes the result via an SMC to Trust Zone
to load and activate the GSI firmware.  When that has completed
successfully, the second stage of initialization can proceed.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_main.c | 1400 ++++++++++++++++++++++++++++++++++++
 1 file changed, 1400 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_main.c

diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c
new file mode 100644
index 000000000000..3d7c59177388
--- /dev/null
+++ b/drivers/net/ipa/ipa_main.c
@@ -0,0 +1,1400 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/remoteproc.h>
+#include <linux/pm_wakeup.h>
+#include <linux/kconfig.h>
+#include <linux/qcom_scm.h>
+#include <linux/soc/qcom/mdt_loader.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/smem_state.h>
+#include <linux/module.h>
+
+#include "ipa_i.h"
+#include "ipa_dma.h"
+#include "ipahal.h"
+
+#define	IPA_CORE_CLOCK_RATE	(75UL * 1000 * 1000)
+
+/* The name of the main firmware file relative to /lib/firmware */
+#define IPA_FWS_PATH		"ipa_fws.mdt"
+#define IPA_PAS_ID		15
+
+#define IPA_APPS_CMD_PROD_RING_COUNT	256
+#define IPA_APPS_LAN_CONS_RING_COUNT	256
+
+/* Details of the initialization sequence are determined by who is
+ * responsible for doing some early IPA hardware initialization.
+ * The Device Tree compatible string defines what to expect.
+ */
+enum ipa_init_type {
+	ipa_undefined_init = 0,
+	ipa_tz_init,
+	ipa_modem_init,
+};
+
+struct ipa_match_data {
+	enum ipa_init_type init_type;
+};
+
+static void ipa_client_remove_deferred(struct work_struct *work);
+static DECLARE_WORK(ipa_client_remove_work, ipa_client_remove_deferred);
+
+static struct ipa_context ipa_ctx_struct;
+struct ipa_context *ipa_ctx = &ipa_ctx_struct;
+
+static int hdr_init_local_cmd(u32 offset, u32 size)
+{
+	struct ipa_desc desc = { };
+	struct ipa_dma_mem mem;
+	void *payload;
+	int ret;
+
+	if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
+		return -ENOMEM;
+
+	offset += ipa_ctx->smem_offset;
+
+	payload = ipahal_hdr_init_local_pyld(&mem, offset);
+	if (!payload) {
+		ret = -ENOMEM;
+		goto err_dma_free;
+	}
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_HDR_INIT_LOCAL;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+err_dma_free:
+	ipa_dma_free(&mem);
+
+	return ret;
+}
+
+static int dma_shared_mem_zero_cmd(u32 offset, u32 size)
+{
+	struct ipa_desc desc = { };
+	struct ipa_dma_mem mem;
+	void *payload;
+	int ret;
+
+	ipa_assert(size > 0);
+
+	if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
+		return -ENOMEM;
+
+	offset += ipa_ctx->smem_offset;
+
+	payload = ipahal_dma_shared_mem_write_pyld(&mem, offset);
+	if (!payload) {
+		ret = -ENOMEM;
+		goto err_dma_free;
+	}
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_DMA_SHARED_MEM;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+err_dma_free:
+	ipa_dma_free(&mem);
+
+	return ret;
+}
+
+/**
+ * ipa_modem_smem_init() - Initialize modem general memory and header memory
+ */
+int ipa_modem_smem_init(void)
+{
+	int ret;
+
+	ret = dma_shared_mem_zero_cmd(IPA_MEM_MODEM_OFST, IPA_MEM_MODEM_SIZE);
+	if (ret)
+		return ret;
+
+	ret = dma_shared_mem_zero_cmd(IPA_MEM_MODEM_HDR_OFST,
+				      IPA_MEM_MODEM_HDR_SIZE);
+	if (ret)
+		return ret;
+
+	return dma_shared_mem_zero_cmd(IPA_MEM_MODEM_HDR_PROC_CTX_OFST,
+				       IPA_MEM_MODEM_HDR_PROC_CTX_SIZE);
+}
+
+static int ipa_ep_apps_cmd_prod_setup(void)
+{
+	enum ipa_client_type dst_client;
+	enum ipa_client_type client;
+	u32 channel_count;
+	u32 ep_id;
+	int ret;
+
+	if (ipa_ctx->cmd_prod_ep_id != IPA_EP_ID_BAD)
+		ret = -EBUSY;
+
+	client = IPA_CLIENT_APPS_CMD_PROD;
+	dst_client = IPA_CLIENT_APPS_LAN_CONS;
+	channel_count = IPA_APPS_CMD_PROD_RING_COUNT;
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		return ret;
+	ep_id = ret;
+
+
+	ipa_endp_init_mode_prod(ep_id, IPA_DMA, dst_client);
+	ipa_endp_init_seq_prod(ep_id);
+	ipa_endp_init_deaggr_prod(ep_id);
+
+	ret = ipa_ep_setup(ep_id, channel_count, 2, 0, NULL, NULL);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		ipa_ctx->cmd_prod_ep_id = ep_id;
+
+	return ret;
+}
+
+/* Only used for IPA_MEM_UC_EVENT_RING_OFST, which must be 1KB aligned */
+static __always_inline void sram_set_canary(u32 *sram_mmio, u32 offset)
+{
+	BUILD_BUG_ON(offset < sizeof(*sram_mmio));
+	BUILD_BUG_ON(offset % 1024);
+
+	sram_mmio += offset / sizeof(*sram_mmio);
+	*--sram_mmio = IPA_MEM_CANARY_VAL;
+}
+
+static __always_inline void sram_set_canaries(u32 *sram_mmio, u32 offset)
+{
+	BUILD_BUG_ON(offset < 2 * sizeof(*sram_mmio));
+	BUILD_BUG_ON(offset % 8);
+
+	sram_mmio += offset / sizeof(*sram_mmio);
+	*--sram_mmio = IPA_MEM_CANARY_VAL;
+	*--sram_mmio = IPA_MEM_CANARY_VAL;
+}
+
+/**
+ * ipa_init_sram() - Initialize IPA local SRAM.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_sram(void)
+{
+	phys_addr_t phys_addr;
+	u32 *ipa_sram_mmio;
+
+	phys_addr = ipa_ctx->ipa_phys;
+	phys_addr += ipa_reg_n_offset(IPA_SRAM_DIRECT_ACCESS_N, 0);
+	phys_addr += ipa_ctx->smem_offset;
+
+	ipa_sram_mmio = ioremap(phys_addr, ipa_ctx->smem_size);
+	if (!ipa_sram_mmio) {
+		ipa_err("fail to ioremap IPA SRAM\n");
+		return -ENOMEM;
+	}
+
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_FLT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_FLT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_FLT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_FLT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_RT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_RT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_RT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_RT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_MODEM_HDR_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_MODEM_HDR_PROC_CTX_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_MODEM_OFST);
+
+	/* Only one canary precedes the microcontroller ring */
+	sram_set_canary(ipa_sram_mmio, IPA_MEM_UC_EVENT_RING_OFST);
+
+	iounmap(ipa_sram_mmio);
+
+	return 0;
+}
+
+/**
+ * ipa_init_hdr() - Initialize IPA header block.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_hdr(void)
+{
+	int ret;
+
+	if (IPA_MEM_MODEM_HDR_SIZE) {
+		ret = hdr_init_local_cmd(IPA_MEM_MODEM_HDR_OFST,
+					 IPA_MEM_MODEM_HDR_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (IPA_MEM_APPS_HDR_SIZE) {
+		BUILD_BUG_ON(IPA_MEM_APPS_HDR_OFST % 8);
+		ret = hdr_init_local_cmd(IPA_MEM_APPS_HDR_OFST,
+					 IPA_MEM_APPS_HDR_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (IPA_MEM_MODEM_HDR_PROC_CTX_SIZE) {
+		ret = dma_shared_mem_zero_cmd(IPA_MEM_MODEM_HDR_PROC_CTX_OFST,
+					      IPA_MEM_MODEM_HDR_PROC_CTX_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (IPA_MEM_APPS_HDR_PROC_CTX_SIZE) {
+		BUILD_BUG_ON(IPA_MEM_APPS_HDR_PROC_CTX_OFST % 8);
+		ret = dma_shared_mem_zero_cmd(IPA_MEM_APPS_HDR_PROC_CTX_OFST,
+					      IPA_MEM_APPS_HDR_PROC_CTX_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	ipa_write_reg(IPA_LOCAL_PKT_PROC_CNTXT_BASE,
+		      ipa_ctx->smem_offset + IPA_MEM_MODEM_HDR_PROC_CTX_OFST);
+
+	return 0;
+}
+
+/**
+ * ipa_init_rt4() - Initialize IPA routing block for IPv4.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_rt4(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_RT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_RT_NHASH_OFST;
+	payload = ipahal_ip_v4_routing_init_pyld(mem, hash_offset,
+						 nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V4_ROUTING_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+/**
+ * ipa_init_rt6() - Initialize IPA routing block for IPv6.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_rt6(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_RT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_RT_NHASH_OFST;
+	payload = ipahal_ip_v6_routing_init_pyld(mem, hash_offset,
+						 nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V6_ROUTING_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+/**
+ * ipa_init_flt4() - Initialize IPA filtering block for IPv4.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_flt4(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_FLT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_FLT_NHASH_OFST;
+	payload = ipahal_ip_v4_filter_init_pyld(mem, hash_offset,
+						nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V4_FILTER_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+/**
+ * ipa_init_flt6() - Initialize IPA filtering block for IPv6.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_flt6(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_FLT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_FLT_NHASH_OFST;
+	payload = ipahal_ip_v6_filter_init_pyld(mem, hash_offset,
+						nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V6_FILTER_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+static void ipa_setup_flt_hash_tuple(void)
+{
+	u32 ep_mask = ipa_ctx->filter_bitmap;
+
+	while (ep_mask) {
+		u32 i = __ffs(ep_mask);
+
+		ep_mask ^= BIT(i);
+		if (!ipa_is_modem_ep(i))
+			ipa_set_flt_tuple_mask(i);
+	}
+}
+
+static void ipa_setup_rt_hash_tuple(void)
+{
+	u32 route_mask;
+	u32 modem_mask;
+
+	BUILD_BUG_ON(!IPA_MEM_MODEM_RT_COUNT);
+	BUILD_BUG_ON(IPA_MEM_RT_COUNT < IPA_MEM_MODEM_RT_COUNT);
+
+	/* Compute a mask representing non-modem route table entries */
+	route_mask = GENMASK(IPA_MEM_RT_COUNT - 1, 0);
+	modem_mask = GENMASK(IPA_MEM_MODEM_RT_INDEX_MAX,
+			     IPA_MEM_MODEM_RT_INDEX_MIN);
+	route_mask &= ~modem_mask;
+
+	while (route_mask) {
+		u32 i = __ffs(route_mask);
+
+		route_mask ^= BIT(i);
+		ipa_set_rt_tuple_mask(i);
+	}
+}
+
+static int ipa_ep_apps_lan_cons_setup(void)
+{
+	enum ipa_client_type client;
+	u32 rx_buffer_size;
+	u32 channel_count;
+	u32 aggr_count;
+	u32 aggr_bytes;
+	u32 aggr_size;
+	u32 ep_id;
+	int ret;
+
+	client = IPA_CLIENT_APPS_LAN_CONS;
+	channel_count = IPA_APPS_LAN_CONS_RING_COUNT;
+	aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT;
+	aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT;
+
+	if (aggr_bytes > ipa_reg_aggr_max_byte_limit())
+		return -EINVAL;
+
+	if (aggr_count > ipa_reg_aggr_max_packet_limit())
+		return -EINVAL;
+
+	if (ipa_ctx->lan_cons_ep_id != IPA_EP_ID_BAD)
+		return -EBUSY;
+
+	/* Compute the buffer size required to handle the requested
+	 * aggregation byte limit.  The aggr_byte_limit value is
+	 * expressed as a number of KB, but we derive that value
+	 * after computing the buffer size to use (in bytes).  The
+	 * buffer must be sufficient to hold one IPA_MTU-sized
+	 * packet *after* the limit is reached.
+	 *
+	 * (Note that the rx_buffer_size value reflects only the
+	 * space for data, not any standard metadata or headers.)
+	 */
+	rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes);
+
+	/* Account for the extra IPA_MTU past the limit in the
+	 * buffer, and convert the result to the KB units the
+	 * aggr_byte_limit uses.
+	 */
+	aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K;
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		return ret;
+	ep_id = ret;
+
+	ipa_endp_init_hdr_cons(ep_id, IPA_LAN_RX_HEADER_LENGTH, 0, 0);
+	ipa_endp_init_hdr_ext_cons(ep_id, ilog2(sizeof(u32)), false);
+	ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, false);
+	ipa_endp_init_cfg_cons(ep_id, IPA_CS_OFFLOAD_DL);
+	ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0x0);
+	ipa_endp_status_cons(ep_id, true);
+
+	ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size,
+			   ipa_lan_rx_cb, NULL);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		ipa_ctx->lan_cons_ep_id = ep_id;
+
+	return ret;
+}
+
+static int ipa_ep_apps_setup(void)
+{
+	struct ipa_dma_mem mem;	/* Empty table */
+	int ret;
+
+	/* CMD OUT (AP->IPA) */
+	ret = ipa_ep_apps_cmd_prod_setup();
+	if (ret < 0)
+		return ret;
+
+	ipa_init_sram();
+	ipa_init_hdr();
+
+	ret = ipahal_rt_generate_empty_img(IPA_MEM_RT_COUNT, &mem);
+	ipa_assert(!ret);
+	ipa_init_rt4(&mem);
+	ipa_init_rt6(&mem);
+	ipahal_free_empty_img(&mem);
+
+	ret = ipahal_flt_generate_empty_img(ipa_ctx->filter_bitmap, &mem);
+	ipa_assert(!ret);
+	ipa_init_flt4(&mem);
+	ipa_init_flt6(&mem);
+	ipahal_free_empty_img(&mem);
+
+	ipa_setup_flt_hash_tuple();
+	ipa_setup_rt_hash_tuple();
+
+	/* LAN IN (IPA->AP)
+	 *
+	 * Even without supporting LAN traffic, we use the LAN consumer
+	 * endpoint for receiving some information from the IPA.  If we issue
+	 * a tagged command, we arrange to be notified of its completion
+	 * through this endpoint.  In addition, we arrange for this endpoint
+	 * to be used as the IPA's default route; the IPA will notify the AP
+	 * of exceptions (unroutable packets, but other events as well)
+	 * through this endpoint.
+	 */
+	ret = ipa_ep_apps_lan_cons_setup();
+	if (ret < 0)
+		goto fail_flt_hash_tuple;
+
+	ipa_cfg_default_route(IPA_CLIENT_APPS_LAN_CONS);
+
+	return 0;
+
+fail_flt_hash_tuple:
+	ipa_ep_teardown(ipa_ctx->cmd_prod_ep_id);
+	ipa_ctx->cmd_prod_ep_id = IPA_EP_ID_BAD;
+
+	return ret;
+}
+
+static int ipa_clock_init(struct device *dev)
+{
+	struct clk *clk;
+	int ret;
+
+	clk = clk_get(dev, "core");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	ret = clk_set_rate(clk, IPA_CORE_CLOCK_RATE);
+	if (ret) {
+		clk_put(clk);
+		return ret;
+	}
+
+	ipa_ctx->core_clock = clk;
+
+	return 0;
+}
+
+static void ipa_clock_exit(void)
+{
+	clk_put(ipa_ctx->core_clock);
+	ipa_ctx->core_clock = NULL;
+}
+
+/**
+ * ipa_enable_clks() - Turn on IPA clocks
+ */
+static void ipa_enable_clks(void)
+{
+	if (WARN_ON(ipa_interconnect_enable()))
+		return;
+
+	if (WARN_ON(clk_prepare_enable(ipa_ctx->core_clock)))
+		ipa_interconnect_disable();
+}
+
+/**
+ * ipa_disable_clks() - Turn off IPA clocks
+ */
+static void ipa_disable_clks(void)
+{
+	clk_disable_unprepare(ipa_ctx->core_clock);
+	WARN_ON(ipa_interconnect_disable());
+}
+
+/* Add an IPA client under protection of the mutex.  This is called
+ * for the first client, but a race could mean another caller gets
+ * the first reference.  When the first reference is taken, IPA
+ * clocks are enabled endpoints are resumed.  A positive reference count
+ * means the endpoints are active; this doesn't set the first reference
+ * until after this is complete (and the mutex, not the atomic
+ * count, is what protects this).
+ */
+static void ipa_client_add_first(void)
+{
+	mutex_lock(&ipa_ctx->active_clients_mutex);
+
+	/* A reference might have been added while awaiting the mutex. */
+	if (!atomic_inc_not_zero(&ipa_ctx->active_clients_count)) {
+		ipa_enable_clks();
+		ipa_ep_resume_all();
+		atomic_inc(&ipa_ctx->active_clients_count);
+	} else {
+		ipa_assert(atomic_read(&ipa_ctx->active_clients_count) > 1);
+	}
+
+	mutex_unlock(&ipa_ctx->active_clients_mutex);
+}
+
+/* Attempt to add an IPA client reference, but only if this does not
+ * represent the initiaal reference.  Returns true if the reference
+ * was taken, false otherwise.
+ */
+static bool ipa_client_add_not_first(void)
+{
+	return !!atomic_inc_not_zero(&ipa_ctx->active_clients_count);
+}
+
+/* Add an IPA client, but only if the reference count is already
+ * non-zero.  (This is used to avoid blocking.)  Returns true if the
+ * additional reference was added successfully, or false otherwise.
+ */
+bool ipa_client_add_additional(void)
+{
+	return ipa_client_add_not_first();
+}
+
+/* Add an IPA client.  If this is not the first client, the
+ * reference count is updated and return is immediate.  Otherwise
+ * ipa_client_add_first() will safely add the first client, enabling
+ * clocks and setting up (resuming) endpoints before returning.
+ */
+void ipa_client_add(void)
+{
+	/* There's nothing more to do if this isn't the first reference */
+	if (!ipa_client_add_not_first())
+		ipa_client_add_first();
+}
+
+/* Remove an IPA client under protection of the mutex.  This is
+ * called for the last remaining client, but a race could mean
+ * another caller gets an additional reference before the mutex
+ * is acquired.  When the final reference is dropped, endpoints are
+ * suspended and IPA clocks disabled.
+ */
+static void ipa_client_remove_final(void)
+{
+	mutex_lock(&ipa_ctx->active_clients_mutex);
+
+	/* A reference might have been removed while awaiting the mutex. */
+	if (!atomic_dec_return(&ipa_ctx->active_clients_count)) {
+		ipa_ep_suspend_all();
+		ipa_disable_clks();
+	}
+
+	mutex_unlock(&ipa_ctx->active_clients_mutex);
+}
+
+/* Decrement the active clients reference count, and if the result
+ * is 0, suspend the endpoints and disable clocks.
+ *
+ * This function runs in work queue context, scheduled to run whenever
+ * the last reference would be dropped in ipa_client_remove().
+ */
+static void ipa_client_remove_deferred(struct work_struct *work)
+{
+	ipa_client_remove_final();
+}
+
+/* Attempt to remove a client reference, but only if this is not the
+ * only reference remaining.  Returns true if the reference was
+ * removed, or false if doing so would produce a zero reference
+ * count.
+ */
+static bool ipa_client_remove_not_final(void)
+{
+	return !!atomic_add_unless(&ipa_ctx->active_clients_count, -1, 1);
+}
+
+/* Attempt to remove an IPA client reference.  If this represents
+ * the last reference arrange for ipa_client_remove_final() to be
+ * called in workqueue context, dropping the last reference under
+ * protection of the mutex.
+ */
+void ipa_client_remove(void)
+{
+	if (!ipa_client_remove_not_final())
+		queue_work(ipa_ctx->power_mgmt_wq, &ipa_client_remove_work);
+}
+
+/** ipa_inc_acquire_wakelock() - Increase active clients counter, and
+ * acquire wakelock if necessary
+ */
+void ipa_inc_acquire_wakelock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipa_ctx->wakeup_lock, flags);
+
+	ipa_ctx->wakeup_count++;
+	if (ipa_ctx->wakeup_count == 1)
+		__pm_stay_awake(&ipa_ctx->wakeup);
+
+	spin_unlock_irqrestore(&ipa_ctx->wakeup_lock, flags);
+}
+
+/** ipa_dec_release_wakelock() - Decrease active clients counter
+ *
+ * In case if the ref count is 0, release the wakelock.
+ */
+void ipa_dec_release_wakelock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipa_ctx->wakeup_lock, flags);
+
+	ipa_ctx->wakeup_count--;
+	if (ipa_ctx->wakeup_count == 0)
+		__pm_relax(&ipa_ctx->wakeup);
+
+	spin_unlock_irqrestore(&ipa_ctx->wakeup_lock, flags);
+}
+
+/** ipa_suspend_handler() - Handle the suspend interrupt
+ * @interrupt:	Interrupt type
+ * @endpoints:	Interrupt specific information data
+ */
+static void ipa_suspend_handler(enum ipa_irq_type interrupt, u32 interrupt_data)
+{
+	u32 endpoints = interrupt_data;
+
+	while (endpoints) {
+		enum ipa_client_type client;
+		u32 i = __ffs(endpoints);
+
+		endpoints ^= BIT(i);
+
+		if (!ipa_ctx->ep[i].allocated)
+			continue;
+
+		client = ipa_ctx->ep[i].client;
+		if (!ipa_ap_consumer(client))
+			continue;
+
+		/* endpoint will be unsuspended by enabling IPA clocks */
+		mutex_lock(&ipa_ctx->transport_pm.transport_pm_mutex);
+		if (!atomic_read(&ipa_ctx->transport_pm.dec_clients)) {
+			ipa_client_add();
+
+			atomic_set(&ipa_ctx->transport_pm.dec_clients, 1);
+		}
+		mutex_unlock(&ipa_ctx->transport_pm.transport_pm_mutex);
+	}
+}
+
+/**
+ * ipa_init_interrupts() - Initialize IPA interrupts
+ */
+static int ipa_init_interrupts(void)
+{
+	int ret;
+
+	ret = ipa_interrupts_init();
+	if (!ret)
+		return ret;
+
+	ipa_add_interrupt_handler(IPA_TX_SUSPEND_IRQ, ipa_suspend_handler);
+
+	return 0;
+}
+
+static void ipa_freeze_clock_vote_and_notify_modem(void)
+{
+	u32 value;
+	u32 mask;
+
+	if (ipa_ctx->smp2p_info.res_sent)
+		return;
+
+	if (!ipa_ctx->smp2p_info.enabled_state) {
+		ipa_err("smp2p out gpio not assigned\n");
+		return;
+	}
+
+	ipa_ctx->smp2p_info.ipa_clk_on = ipa_client_add_additional();
+
+	/* Signal whether the clock is enabled */
+	mask = BIT(ipa_ctx->smp2p_info.enabled_bit);
+	value = ipa_ctx->smp2p_info.ipa_clk_on ? mask : 0;
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.enabled_state, mask,
+				    value);
+
+	/* Now indicate that the enabled flag is valid */
+	mask = BIT(ipa_ctx->smp2p_info.valid_bit);
+	value = mask;
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.valid_state, mask,
+				    value);
+
+	ipa_ctx->smp2p_info.res_sent = true;
+}
+
+void ipa_reset_freeze_vote(void)
+{
+	u32 mask;
+
+	if (!ipa_ctx->smp2p_info.res_sent)
+		return;
+
+	if (ipa_ctx->smp2p_info.ipa_clk_on)
+		ipa_client_remove();
+
+	/* Reset the clock enabled valid flag */
+	mask = BIT(ipa_ctx->smp2p_info.valid_bit);
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.valid_state, mask, 0);
+
+	/* Mark the clock disabled for good measure... */
+	mask = BIT(ipa_ctx->smp2p_info.enabled_bit);
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.enabled_state, mask, 0);
+
+	ipa_ctx->smp2p_info.res_sent = false;
+	ipa_ctx->smp2p_info.ipa_clk_on = false;
+}
+
+static int
+ipa_panic_notifier(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	ipa_freeze_clock_vote_and_notify_modem();
+	ipa_uc_panic_notifier();
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ipa_panic_blk = {
+	.notifier_call = ipa_panic_notifier,
+	/* IPA panic handler needs to run before modem shuts down */
+	.priority = INT_MAX,
+};
+
+static void ipa_register_panic_hdlr(void)
+{
+	atomic_notifier_chain_register(&panic_notifier_list, &ipa_panic_blk);
+}
+
+/* Remoteproc callbacks for SSR events: prepare, start, stop, unprepare */
+int ipa_ssr_prepare(struct rproc_subdev *subdev)
+{
+	printk("======== SSR prepare received ========\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_prepare);
+
+int ipa_ssr_start(struct rproc_subdev *subdev)
+{
+	printk("======== SSR start received ========\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_start);
+
+void ipa_ssr_stop(struct rproc_subdev *subdev, bool crashed)
+{
+	printk("======== SSR stop received ========\n");
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_stop);
+
+void ipa_ssr_unprepare(struct rproc_subdev *subdev)
+{
+	printk("======== SSR unprepare received ========\n");
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_unprepare);
+
+/**
+ * ipa_post_init() - Initialize the IPA Driver (Part II).
+ *
+ * Perform initialization that requires interaction with IPA hardware.
+ */
+static void ipa_post_init(void)
+{
+	int ret;
+
+	ipa_debug("ipa_post_init() started\n");
+
+	ret = gsi_device_init(ipa_ctx->gsi);
+	if (ret) {
+		ipa_err(":gsi register error - %d\n", ret);
+		return;
+	}
+
+	/* setup the AP-IPA endpoints */
+	if (ipa_ep_apps_setup()) {
+		ipa_err(":failed to setup IPA-Apps endpoints\n");
+		gsi_device_exit(ipa_ctx->gsi);
+
+		return;
+	}
+
+	ipa_ctx->uc_ctx = ipa_uc_init(ipa_ctx->ipa_phys);
+	if (!ipa_ctx->uc_ctx)
+		ipa_err("microcontroller init failed\n");
+
+	ipa_register_panic_hdlr();
+
+	ipa_ctx->modem_clk_vote_valid = true;
+
+	if (ipa_wwan_init())
+		ipa_err("WWAN init failed (ignoring)\n");
+
+	dev_info(ipa_ctx->dev, "IPA driver initialization was successful.\n");
+}
+
+/** ipa_pre_init() - Initialize the IPA Driver.
+ *
+ * Perform initialization which doesn't require access to IPA hardware.
+ */
+static int ipa_pre_init(void)
+{
+	int ret = 0;
+
+	/* enable IPA clocks explicitly to allow the initialization */
+	ipa_enable_clks();
+
+	ipa_init_hw();
+
+	ipa_ctx->ep_count = ipa_get_ep_count();
+	ipa_debug("ep_count %u\n", ipa_get_ep_count());
+	ipa_assert(ipa_ctx->ep_count <= IPA_EP_COUNT_MAX);
+
+	ipa_sram_settings_read();
+	if (ipa_ctx->smem_size < IPA_MEM_END_OFST) {
+		ipa_err("insufficient memory: %hu bytes available, need %u\n",
+			ipa_ctx->smem_size, IPA_MEM_END_OFST);
+		ret = -ENOMEM;
+		goto err_disable_clks;
+	}
+
+	mutex_init(&ipa_ctx->active_clients_mutex);
+	atomic_set(&ipa_ctx->active_clients_count, 1);
+
+	/* Create workqueues for power management */
+	ipa_ctx->power_mgmt_wq =
+		create_singlethread_workqueue("ipa_power_mgmt");
+	if (!ipa_ctx->power_mgmt_wq) {
+		ipa_err("failed to create power mgmt wq\n");
+		ret = -ENOMEM;
+		goto err_disable_clks;
+	}
+
+	mutex_init(&ipa_ctx->transport_pm.transport_pm_mutex);
+
+	/* init the lookaside cache */
+
+	ipa_ctx->dp = ipa_dp_init();
+	if (!ipa_ctx->dp)
+		goto err_destroy_pm_wq;
+
+	/* allocate memory for DMA_TASK workaround */
+	ret = ipa_gsi_dma_task_alloc();
+	if (ret)
+		goto err_dp_exit;
+
+	/* Create a wakeup source. */
+	wakeup_source_init(&ipa_ctx->wakeup, "IPA_WS");
+	spin_lock_init(&ipa_ctx->wakeup_lock);
+
+	/* Note enabling dynamic clock division must not be
+	 * attempted for IPA hardware versions prior to 3.5.
+	 */
+	ipa_enable_dcd();
+
+	/* Assign resource limitation to each group */
+	ipa_set_resource_groups_min_max_limits();
+
+	ret = ipa_init_interrupts();
+	if (!ret)
+		return 0;	/* Success! */
+
+	ipa_err("ipa initialization of interrupts failed\n");
+err_dp_exit:
+	ipa_dp_exit(ipa_ctx->dp);
+	ipa_ctx->dp = NULL;
+err_destroy_pm_wq:
+	destroy_workqueue(ipa_ctx->power_mgmt_wq);
+err_disable_clks:
+	ipa_disable_clks();
+
+	return ret;
+}
+
+static int ipa_firmware_load(struct device *dev)
+{
+	const struct firmware *fw;
+	struct device_node *node;
+	struct resource res;
+	phys_addr_t phys;
+	ssize_t size;
+	void *virt;
+	int ret;
+
+	ret = request_firmware(&fw, IPA_FWS_PATH, dev);
+	if (ret)
+		return ret;
+
+	node = of_parse_phandle(dev->of_node, "memory-region", 0);
+	if (!node) {
+		dev_err(dev, "memory-region not specified\n");
+		ret = -EINVAL;
+		goto out_release_firmware;
+	}
+
+	ret = of_address_to_resource(node, 0, &res);
+	if (ret)
+		goto out_release_firmware;
+
+	phys = res.start,
+	size = (size_t)resource_size(&res);
+	virt = memremap(phys, size, MEMREMAP_WC);
+	if (!virt) {
+		ret = -ENOMEM;
+		goto out_release_firmware;
+	}
+
+	ret = qcom_mdt_load(dev, fw, IPA_FWS_PATH, IPA_PAS_ID,
+			    virt, phys, size, NULL);
+	if (!ret)
+		ret = qcom_scm_pas_auth_and_reset(IPA_PAS_ID);
+
+	memunmap(virt);
+out_release_firmware:
+	release_firmware(fw);
+
+	return ret;
+}
+
+/* Threaded IRQ handler for modem "ipa-clock-query" SMP2P interrupt */
+static irqreturn_t ipa_smp2p_modem_clk_query_isr(int irq, void *ctxt)
+{
+	ipa_freeze_clock_vote_and_notify_modem();
+
+	return IRQ_HANDLED;
+}
+
+/* Threaded IRQ handler for modem "ipa-post-init" SMP2P interrupt */
+static irqreturn_t ipa_smp2p_modem_post_init_isr(int irq, void *ctxt)
+{
+	ipa_post_init();
+
+	return IRQ_HANDLED;
+}
+
+static int
+ipa_smp2p_irq_init(struct device *dev, const char *name, irq_handler_t handler)
+{
+	struct device_node *node = dev->of_node;
+	unsigned int irq;
+	int ret;
+
+	ret = of_irq_get_byname(node, name);
+	if (ret < 0)
+		return ret;
+	if (!ret)
+		return -EINVAL;		/* IRQ mapping failure */
+	irq = ret;
+
+	ret = devm_request_threaded_irq(dev, irq, NULL, handler, 0, name, dev);
+	if (ret)
+		return ret;
+
+	return irq;
+}
+
+static void
+ipa_smp2p_irq_exit(struct device *dev, unsigned int irq)
+{
+	devm_free_irq(dev, irq, dev);
+}
+
+static int ipa_smp2p_init(struct device *dev, bool modem_init)
+{
+	struct qcom_smem_state *enabled_state;
+	struct qcom_smem_state *valid_state;
+	struct device_node *node;
+	unsigned int enabled_bit;
+	unsigned int valid_bit;
+	unsigned int clock_irq;
+	int ret;
+
+	node = dev->of_node;
+
+	valid_state = qcom_smem_state_get(dev, "ipa-clock-enabled-valid",
+					  &valid_bit);
+	if (IS_ERR(valid_state))
+		return PTR_ERR(valid_state);
+
+	enabled_state = qcom_smem_state_get(dev, "ipa-clock-enabled",
+					    &enabled_bit);
+	if (IS_ERR(enabled_state)) {
+		ret = PTR_ERR(enabled_state);
+		ipa_err("error %d getting ipa-clock-enabled state\n", ret);
+
+		return ret;
+	}
+
+	ret = ipa_smp2p_irq_init(dev, "ipa-clock-query",
+				 ipa_smp2p_modem_clk_query_isr);
+	if (ret < 0)
+		return ret;
+	clock_irq = ret;
+
+	if (modem_init) {
+		/* Result will be non-zero (negative for error) */
+		ret = ipa_smp2p_irq_init(dev, "ipa-post-init",
+					 ipa_smp2p_modem_post_init_isr);
+		if (ret < 0) {
+			ipa_smp2p_irq_exit(dev, clock_irq);
+
+			return ret;
+		}
+	}
+
+	/* Success.  Record our smp2p information */
+	ipa_ctx->smp2p_info.valid_state = valid_state;
+	ipa_ctx->smp2p_info.valid_bit = valid_bit;
+	ipa_ctx->smp2p_info.enabled_state = enabled_state;
+	ipa_ctx->smp2p_info.enabled_bit = enabled_bit;
+	ipa_ctx->smp2p_info.clock_query_irq = clock_irq;
+	ipa_ctx->smp2p_info.post_init_irq = modem_init ? ret : 0;
+
+	return 0;
+}
+
+static void ipa_smp2p_exit(struct device *dev)
+{
+	if (ipa_ctx->smp2p_info.post_init_irq)
+		ipa_smp2p_irq_exit(dev, ipa_ctx->smp2p_info.post_init_irq);
+	ipa_smp2p_irq_exit(dev, ipa_ctx->smp2p_info.clock_query_irq);
+
+	memset(&ipa_ctx->smp2p_info, 0, sizeof(ipa_ctx->smp2p_info));
+}
+
+static const struct ipa_match_data tz_init = {
+	.init_type = ipa_tz_init,
+};
+
+static const struct ipa_match_data modem_init = {
+	.init_type = ipa_modem_init,
+};
+
+static const struct of_device_id ipa_plat_drv_match[] = {
+	{
+		.compatible = "qcom,ipa-sdm845-tz_init",
+		.data = &tz_init,
+	},
+	{
+		.compatible = "qcom,ipa-sdm845-modem_init",
+		.data = &modem_init,
+	},
+	{}
+};
+
+static int ipa_plat_drv_probe(struct platform_device *pdev)
+{
+	const struct ipa_match_data *match_data;
+	struct resource *res;
+	struct device *dev;
+	bool modem_init;
+	int ret;
+
+	/* We assume we're working on 64-bit hardware */
+	BUILD_BUG_ON(!IS_ENABLED(CONFIG_64BIT));
+
+	dev = &pdev->dev;
+
+	match_data = of_device_get_match_data(dev);
+	modem_init = match_data->init_type == ipa_modem_init;
+
+	/* If we need Trust Zone, make sure it's ready */
+	if (!modem_init)
+		if (!qcom_scm_is_available())
+			return -EPROBE_DEFER;
+
+	/* Initialize the smp2p driver early.  It might not be ready
+	 * when we're probed, so it might return -EPROBE_DEFER.
+	 */
+	ret = ipa_smp2p_init(dev, modem_init);
+	if (ret)
+		return ret;
+
+	/* Initialize the interconnect driver early too.  It might
+	 * also return -EPROBE_DEFER.
+	 */
+	ret = ipa_interconnect_init(dev);
+	if (ret)
+		goto out_smp2p_exit;
+
+	ret = ipa_clock_init(dev);
+	if (ret)
+		goto err_interconnect_exit;
+
+	ipa_ctx->dev = dev;	/* Set early for ipa_err()/ipa_debug() */
+
+	/* Compute a bitmask representing which endpoints support filtering */
+	ipa_ctx->filter_bitmap = ipa_filter_bitmap_init();
+	ipa_debug("filter_bitmap 0x%08x\n", ipa_ctx->filter_bitmap);
+	if (!ipa_ctx->filter_bitmap)
+		goto err_clock_exit;
+
+	ret = platform_get_irq_byname(pdev, "ipa");
+	if (ret < 0)
+		goto err_clear_filter_bitmap;
+	ipa_ctx->ipa_irq = ret;
+
+	/* Get IPA memory range */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ipa");
+	if (!res) {
+		ret = -ENODEV;
+		goto err_clear_ipa_irq;
+	}
+
+	/* Setup IPA register access */
+	ret = ipa_reg_init(res->start, (size_t)resource_size(res));
+	if (ret)
+		goto err_clear_ipa_irq;
+	ipa_ctx->ipa_phys = res->start;
+
+	ipa_ctx->gsi = gsi_init(pdev);
+	if (IS_ERR(ipa_ctx->gsi)) {
+		ret = PTR_ERR(ipa_ctx->gsi);
+		goto err_clear_gsi;
+	}
+
+	ret = ipa_dma_init(dev, IPA_HW_TBL_SYSADDR_ALIGN);
+	if (ret)
+		goto err_clear_gsi;
+
+	ret = ipahal_init();
+	if (ret)
+		goto err_dma_exit;
+
+	ipa_ctx->cmd_prod_ep_id = IPA_EP_ID_BAD;
+	ipa_ctx->lan_cons_ep_id = IPA_EP_ID_BAD;
+
+	/* Proceed to real initialization */
+	ret = ipa_pre_init();
+	if (ret)
+		goto err_clear_dev;
+
+	/* If the modem is not verifying and loading firmware we need to
+	 * get it loaded ourselves.  Only then can we proceed with the
+	 * second stage of IPA initialization.  If the modem is doing it,
+	 * it will send an SMP2P interrupt to signal this has been done,
+	 * and that will trigger the "post init".
+	 */
+	if (!modem_init) {
+		ret = ipa_firmware_load(dev);
+		if (ret)
+			goto err_clear_dev;
+
+		/* Now we can proceed to stage two initialization */
+		ipa_post_init();
+	}
+
+	return 0;	/* Success */
+
+err_clear_dev:
+	ipa_ctx->lan_cons_ep_id = 0;
+	ipa_ctx->cmd_prod_ep_id = 0;
+	ipahal_exit();
+err_dma_exit:
+	ipa_dma_exit();
+err_clear_gsi:
+	ipa_ctx->gsi = NULL;
+	ipa_ctx->ipa_phys = 0;
+	ipa_reg_exit();
+err_clear_ipa_irq:
+	ipa_ctx->ipa_irq = 0;
+err_clear_filter_bitmap:
+	ipa_ctx->filter_bitmap = 0;
+err_interconnect_exit:
+	ipa_interconnect_exit();
+err_clock_exit:
+	ipa_clock_exit();
+	ipa_ctx->dev = NULL;
+out_smp2p_exit:
+	ipa_smp2p_exit(dev);
+
+	return ret;
+}
+
+static int ipa_plat_drv_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	ipa_ctx->dev = NULL;
+	ipahal_exit();
+	ipa_dma_exit();
+	ipa_ctx->gsi = NULL;	/* XXX ipa_gsi_exit() */
+	ipa_reg_exit();
+
+	ipa_ctx->ipa_phys = 0;
+
+	if (ipa_ctx->lan_cons_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_free(ipa_ctx->lan_cons_ep_id);
+		ipa_ctx->lan_cons_ep_id = IPA_EP_ID_BAD;
+	}
+	if (ipa_ctx->cmd_prod_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_free(ipa_ctx->cmd_prod_ep_id);
+		ipa_ctx->cmd_prod_ep_id = IPA_EP_ID_BAD;
+	}
+	ipa_ctx->ipa_irq = 0;	/* XXX Need to de-initialize? */
+	ipa_ctx->filter_bitmap = 0;
+	ipa_interconnect_exit();
+	ipa_smp2p_exit(dev);
+
+	return 0;
+}
+
+/**
+ * ipa_ap_suspend() - suspend callback for runtime_pm
+ * @dev:	IPA device structure
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP suspend
+ * operation is invoked, usually by pressing a suspend button.
+ *
+ * Return: 	0 if successful, -EAGAIN if IPA is in use
+ */
+int ipa_ap_suspend(struct device *dev)
+{
+	u32 i;
+
+	/* In case there is a tx/rx handler in polling mode fail to suspend */
+	for (i = 0; i < ipa_ctx->ep_count; i++) {
+		if (ipa_ctx->ep[i].sys && ipa_ep_polling(&ipa_ctx->ep[i])) {
+			ipa_err("EP %d is in polling state, do not suspend\n",
+				i);
+			return -EAGAIN;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * ipa_ap_resume() - resume callback for runtime_pm
+ * @dev:	IPA device structure
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP resume
+ * operation is invoked.
+ *
+ * Return:	Zero
+ */
+int ipa_ap_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops ipa_pm_ops = {
+	.suspend_noirq = ipa_ap_suspend,
+	.resume_noirq = ipa_ap_resume,
+};
+
+static struct platform_driver ipa_plat_drv = {
+	.probe = ipa_plat_drv_probe,
+	.remove = ipa_plat_drv_remove,
+	.driver = {
+		.name = "ipa",
+		.owner = THIS_MODULE,
+		.pm = &ipa_pm_ops,
+		.of_match_table = ipa_plat_drv_match,
+	},
+};
+
+builtin_platform_driver(ipa_plat_drv);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("IPA HW device driver");
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch includes "ipa_main.c", which consists mostly of the
initialization code.

The IPA is a hardware resource shared by multiple independent
execution environments (currently, the AP and the modem).  In some
cases, initialization must be performed by only one of these.  As an
example, the AP must initialize some filter table data structures
that are only used by the modem.  (And in general, some initialization
of IPA hardware is required regardless of whether it will be used.)

There are two phases of IPA initialization.  The first phase is
triggered by the probe of the driver.  It involves setting up
operating system resources, and doing some basic initialization
of IPA memory resources using register and DMA access.

The second phase involves configuration of enpoints used, and this
phase requires access to the GSI layer.  However the GSI layer is
requires some firmware to be loaded before it can be used.  So
the second stage (in ipa_post_init()) only occurs after it is known
firmware is loaded.

The GSI firmware can be loaded in two ways:  the modem can load it;
or Trust Zone code running on the AP can load it.  If the modem
loads the firmware, it will send an SMP2P interrupt to the AP to
signal that GSI firmware is loaded and the AP can proceed with its
second stage IPA initialization.  If Trust Zone is responsible for
loading the firmware, the IPA driver requests the firmware blob
from the file system and passes the result via an SMC to Trust Zone
to load and activate the GSI firmware.  When that has completed
successfully, the second stage of initialization can proceed.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_main.c | 1400 ++++++++++++++++++++++++++++++++++++
 1 file changed, 1400 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_main.c

diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c
new file mode 100644
index 000000000000..3d7c59177388
--- /dev/null
+++ b/drivers/net/ipa/ipa_main.c
@@ -0,0 +1,1400 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/remoteproc.h>
+#include <linux/pm_wakeup.h>
+#include <linux/kconfig.h>
+#include <linux/qcom_scm.h>
+#include <linux/soc/qcom/mdt_loader.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/smem_state.h>
+#include <linux/module.h>
+
+#include "ipa_i.h"
+#include "ipa_dma.h"
+#include "ipahal.h"
+
+#define	IPA_CORE_CLOCK_RATE	(75UL * 1000 * 1000)
+
+/* The name of the main firmware file relative to /lib/firmware */
+#define IPA_FWS_PATH		"ipa_fws.mdt"
+#define IPA_PAS_ID		15
+
+#define IPA_APPS_CMD_PROD_RING_COUNT	256
+#define IPA_APPS_LAN_CONS_RING_COUNT	256
+
+/* Details of the initialization sequence are determined by who is
+ * responsible for doing some early IPA hardware initialization.
+ * The Device Tree compatible string defines what to expect.
+ */
+enum ipa_init_type {
+	ipa_undefined_init = 0,
+	ipa_tz_init,
+	ipa_modem_init,
+};
+
+struct ipa_match_data {
+	enum ipa_init_type init_type;
+};
+
+static void ipa_client_remove_deferred(struct work_struct *work);
+static DECLARE_WORK(ipa_client_remove_work, ipa_client_remove_deferred);
+
+static struct ipa_context ipa_ctx_struct;
+struct ipa_context *ipa_ctx = &ipa_ctx_struct;
+
+static int hdr_init_local_cmd(u32 offset, u32 size)
+{
+	struct ipa_desc desc = { };
+	struct ipa_dma_mem mem;
+	void *payload;
+	int ret;
+
+	if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
+		return -ENOMEM;
+
+	offset += ipa_ctx->smem_offset;
+
+	payload = ipahal_hdr_init_local_pyld(&mem, offset);
+	if (!payload) {
+		ret = -ENOMEM;
+		goto err_dma_free;
+	}
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_HDR_INIT_LOCAL;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+err_dma_free:
+	ipa_dma_free(&mem);
+
+	return ret;
+}
+
+static int dma_shared_mem_zero_cmd(u32 offset, u32 size)
+{
+	struct ipa_desc desc = { };
+	struct ipa_dma_mem mem;
+	void *payload;
+	int ret;
+
+	ipa_assert(size > 0);
+
+	if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
+		return -ENOMEM;
+
+	offset += ipa_ctx->smem_offset;
+
+	payload = ipahal_dma_shared_mem_write_pyld(&mem, offset);
+	if (!payload) {
+		ret = -ENOMEM;
+		goto err_dma_free;
+	}
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_DMA_SHARED_MEM;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+err_dma_free:
+	ipa_dma_free(&mem);
+
+	return ret;
+}
+
+/**
+ * ipa_modem_smem_init() - Initialize modem general memory and header memory
+ */
+int ipa_modem_smem_init(void)
+{
+	int ret;
+
+	ret = dma_shared_mem_zero_cmd(IPA_MEM_MODEM_OFST, IPA_MEM_MODEM_SIZE);
+	if (ret)
+		return ret;
+
+	ret = dma_shared_mem_zero_cmd(IPA_MEM_MODEM_HDR_OFST,
+				      IPA_MEM_MODEM_HDR_SIZE);
+	if (ret)
+		return ret;
+
+	return dma_shared_mem_zero_cmd(IPA_MEM_MODEM_HDR_PROC_CTX_OFST,
+				       IPA_MEM_MODEM_HDR_PROC_CTX_SIZE);
+}
+
+static int ipa_ep_apps_cmd_prod_setup(void)
+{
+	enum ipa_client_type dst_client;
+	enum ipa_client_type client;
+	u32 channel_count;
+	u32 ep_id;
+	int ret;
+
+	if (ipa_ctx->cmd_prod_ep_id != IPA_EP_ID_BAD)
+		ret = -EBUSY;
+
+	client = IPA_CLIENT_APPS_CMD_PROD;
+	dst_client = IPA_CLIENT_APPS_LAN_CONS;
+	channel_count = IPA_APPS_CMD_PROD_RING_COUNT;
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		return ret;
+	ep_id = ret;
+
+
+	ipa_endp_init_mode_prod(ep_id, IPA_DMA, dst_client);
+	ipa_endp_init_seq_prod(ep_id);
+	ipa_endp_init_deaggr_prod(ep_id);
+
+	ret = ipa_ep_setup(ep_id, channel_count, 2, 0, NULL, NULL);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		ipa_ctx->cmd_prod_ep_id = ep_id;
+
+	return ret;
+}
+
+/* Only used for IPA_MEM_UC_EVENT_RING_OFST, which must be 1KB aligned */
+static __always_inline void sram_set_canary(u32 *sram_mmio, u32 offset)
+{
+	BUILD_BUG_ON(offset < sizeof(*sram_mmio));
+	BUILD_BUG_ON(offset % 1024);
+
+	sram_mmio += offset / sizeof(*sram_mmio);
+	*--sram_mmio = IPA_MEM_CANARY_VAL;
+}
+
+static __always_inline void sram_set_canaries(u32 *sram_mmio, u32 offset)
+{
+	BUILD_BUG_ON(offset < 2 * sizeof(*sram_mmio));
+	BUILD_BUG_ON(offset % 8);
+
+	sram_mmio += offset / sizeof(*sram_mmio);
+	*--sram_mmio = IPA_MEM_CANARY_VAL;
+	*--sram_mmio = IPA_MEM_CANARY_VAL;
+}
+
+/**
+ * ipa_init_sram() - Initialize IPA local SRAM.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_sram(void)
+{
+	phys_addr_t phys_addr;
+	u32 *ipa_sram_mmio;
+
+	phys_addr = ipa_ctx->ipa_phys;
+	phys_addr += ipa_reg_n_offset(IPA_SRAM_DIRECT_ACCESS_N, 0);
+	phys_addr += ipa_ctx->smem_offset;
+
+	ipa_sram_mmio = ioremap(phys_addr, ipa_ctx->smem_size);
+	if (!ipa_sram_mmio) {
+		ipa_err("fail to ioremap IPA SRAM\n");
+		return -ENOMEM;
+	}
+
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_FLT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_FLT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_FLT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_FLT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_RT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V4_RT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_RT_HASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_V6_RT_NHASH_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_MODEM_HDR_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_MODEM_HDR_PROC_CTX_OFST);
+	sram_set_canaries(ipa_sram_mmio, IPA_MEM_MODEM_OFST);
+
+	/* Only one canary precedes the microcontroller ring */
+	sram_set_canary(ipa_sram_mmio, IPA_MEM_UC_EVENT_RING_OFST);
+
+	iounmap(ipa_sram_mmio);
+
+	return 0;
+}
+
+/**
+ * ipa_init_hdr() - Initialize IPA header block.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_hdr(void)
+{
+	int ret;
+
+	if (IPA_MEM_MODEM_HDR_SIZE) {
+		ret = hdr_init_local_cmd(IPA_MEM_MODEM_HDR_OFST,
+					 IPA_MEM_MODEM_HDR_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (IPA_MEM_APPS_HDR_SIZE) {
+		BUILD_BUG_ON(IPA_MEM_APPS_HDR_OFST % 8);
+		ret = hdr_init_local_cmd(IPA_MEM_APPS_HDR_OFST,
+					 IPA_MEM_APPS_HDR_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (IPA_MEM_MODEM_HDR_PROC_CTX_SIZE) {
+		ret = dma_shared_mem_zero_cmd(IPA_MEM_MODEM_HDR_PROC_CTX_OFST,
+					      IPA_MEM_MODEM_HDR_PROC_CTX_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	if (IPA_MEM_APPS_HDR_PROC_CTX_SIZE) {
+		BUILD_BUG_ON(IPA_MEM_APPS_HDR_PROC_CTX_OFST % 8);
+		ret = dma_shared_mem_zero_cmd(IPA_MEM_APPS_HDR_PROC_CTX_OFST,
+					      IPA_MEM_APPS_HDR_PROC_CTX_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	ipa_write_reg(IPA_LOCAL_PKT_PROC_CNTXT_BASE,
+		      ipa_ctx->smem_offset + IPA_MEM_MODEM_HDR_PROC_CTX_OFST);
+
+	return 0;
+}
+
+/**
+ * ipa_init_rt4() - Initialize IPA routing block for IPv4.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_rt4(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_RT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_RT_NHASH_OFST;
+	payload = ipahal_ip_v4_routing_init_pyld(mem, hash_offset,
+						 nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V4_ROUTING_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+/**
+ * ipa_init_rt6() - Initialize IPA routing block for IPv6.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_rt6(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_RT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_RT_NHASH_OFST;
+	payload = ipahal_ip_v6_routing_init_pyld(mem, hash_offset,
+						 nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V6_ROUTING_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+/**
+ * ipa_init_flt4() - Initialize IPA filtering block for IPv4.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_flt4(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_FLT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V4_FLT_NHASH_OFST;
+	payload = ipahal_ip_v4_filter_init_pyld(mem, hash_offset,
+						nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V4_FILTER_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+/**
+ * ipa_init_flt6() - Initialize IPA filtering block for IPv6.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+static int ipa_init_flt6(struct ipa_dma_mem *mem)
+{
+	struct ipa_desc desc = { };
+	u32 nhash_offset;
+	u32 hash_offset;
+	void *payload;
+	int ret;
+
+	hash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_FLT_HASH_OFST;
+	nhash_offset = ipa_ctx->smem_offset + IPA_MEM_V6_FLT_NHASH_OFST;
+	payload = ipahal_ip_v6_filter_init_pyld(mem, hash_offset,
+						nhash_offset);
+	if (!payload)
+		return -ENOMEM;
+
+	desc.type = IPA_IMM_CMD_DESC;
+	desc.len_opcode = IPA_IMM_CMD_IP_V6_FILTER_INIT;
+	desc.payload = payload;
+
+	ret = ipa_send_cmd(&desc);
+
+	ipahal_payload_free(payload);
+
+	return ret;
+}
+
+static void ipa_setup_flt_hash_tuple(void)
+{
+	u32 ep_mask = ipa_ctx->filter_bitmap;
+
+	while (ep_mask) {
+		u32 i = __ffs(ep_mask);
+
+		ep_mask ^= BIT(i);
+		if (!ipa_is_modem_ep(i))
+			ipa_set_flt_tuple_mask(i);
+	}
+}
+
+static void ipa_setup_rt_hash_tuple(void)
+{
+	u32 route_mask;
+	u32 modem_mask;
+
+	BUILD_BUG_ON(!IPA_MEM_MODEM_RT_COUNT);
+	BUILD_BUG_ON(IPA_MEM_RT_COUNT < IPA_MEM_MODEM_RT_COUNT);
+
+	/* Compute a mask representing non-modem route table entries */
+	route_mask = GENMASK(IPA_MEM_RT_COUNT - 1, 0);
+	modem_mask = GENMASK(IPA_MEM_MODEM_RT_INDEX_MAX,
+			     IPA_MEM_MODEM_RT_INDEX_MIN);
+	route_mask &= ~modem_mask;
+
+	while (route_mask) {
+		u32 i = __ffs(route_mask);
+
+		route_mask ^= BIT(i);
+		ipa_set_rt_tuple_mask(i);
+	}
+}
+
+static int ipa_ep_apps_lan_cons_setup(void)
+{
+	enum ipa_client_type client;
+	u32 rx_buffer_size;
+	u32 channel_count;
+	u32 aggr_count;
+	u32 aggr_bytes;
+	u32 aggr_size;
+	u32 ep_id;
+	int ret;
+
+	client = IPA_CLIENT_APPS_LAN_CONS;
+	channel_count = IPA_APPS_LAN_CONS_RING_COUNT;
+	aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT;
+	aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT;
+
+	if (aggr_bytes > ipa_reg_aggr_max_byte_limit())
+		return -EINVAL;
+
+	if (aggr_count > ipa_reg_aggr_max_packet_limit())
+		return -EINVAL;
+
+	if (ipa_ctx->lan_cons_ep_id != IPA_EP_ID_BAD)
+		return -EBUSY;
+
+	/* Compute the buffer size required to handle the requested
+	 * aggregation byte limit.  The aggr_byte_limit value is
+	 * expressed as a number of KB, but we derive that value
+	 * after computing the buffer size to use (in bytes).  The
+	 * buffer must be sufficient to hold one IPA_MTU-sized
+	 * packet *after* the limit is reached.
+	 *
+	 * (Note that the rx_buffer_size value reflects only the
+	 * space for data, not any standard metadata or headers.)
+	 */
+	rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes);
+
+	/* Account for the extra IPA_MTU past the limit in the
+	 * buffer, and convert the result to the KB units the
+	 * aggr_byte_limit uses.
+	 */
+	aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K;
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		return ret;
+	ep_id = ret;
+
+	ipa_endp_init_hdr_cons(ep_id, IPA_LAN_RX_HEADER_LENGTH, 0, 0);
+	ipa_endp_init_hdr_ext_cons(ep_id, ilog2(sizeof(u32)), false);
+	ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, false);
+	ipa_endp_init_cfg_cons(ep_id, IPA_CS_OFFLOAD_DL);
+	ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0x0);
+	ipa_endp_status_cons(ep_id, true);
+
+	ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size,
+			   ipa_lan_rx_cb, NULL);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		ipa_ctx->lan_cons_ep_id = ep_id;
+
+	return ret;
+}
+
+static int ipa_ep_apps_setup(void)
+{
+	struct ipa_dma_mem mem;	/* Empty table */
+	int ret;
+
+	/* CMD OUT (AP->IPA) */
+	ret = ipa_ep_apps_cmd_prod_setup();
+	if (ret < 0)
+		return ret;
+
+	ipa_init_sram();
+	ipa_init_hdr();
+
+	ret = ipahal_rt_generate_empty_img(IPA_MEM_RT_COUNT, &mem);
+	ipa_assert(!ret);
+	ipa_init_rt4(&mem);
+	ipa_init_rt6(&mem);
+	ipahal_free_empty_img(&mem);
+
+	ret = ipahal_flt_generate_empty_img(ipa_ctx->filter_bitmap, &mem);
+	ipa_assert(!ret);
+	ipa_init_flt4(&mem);
+	ipa_init_flt6(&mem);
+	ipahal_free_empty_img(&mem);
+
+	ipa_setup_flt_hash_tuple();
+	ipa_setup_rt_hash_tuple();
+
+	/* LAN IN (IPA->AP)
+	 *
+	 * Even without supporting LAN traffic, we use the LAN consumer
+	 * endpoint for receiving some information from the IPA.  If we issue
+	 * a tagged command, we arrange to be notified of its completion
+	 * through this endpoint.  In addition, we arrange for this endpoint
+	 * to be used as the IPA's default route; the IPA will notify the AP
+	 * of exceptions (unroutable packets, but other events as well)
+	 * through this endpoint.
+	 */
+	ret = ipa_ep_apps_lan_cons_setup();
+	if (ret < 0)
+		goto fail_flt_hash_tuple;
+
+	ipa_cfg_default_route(IPA_CLIENT_APPS_LAN_CONS);
+
+	return 0;
+
+fail_flt_hash_tuple:
+	ipa_ep_teardown(ipa_ctx->cmd_prod_ep_id);
+	ipa_ctx->cmd_prod_ep_id = IPA_EP_ID_BAD;
+
+	return ret;
+}
+
+static int ipa_clock_init(struct device *dev)
+{
+	struct clk *clk;
+	int ret;
+
+	clk = clk_get(dev, "core");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	ret = clk_set_rate(clk, IPA_CORE_CLOCK_RATE);
+	if (ret) {
+		clk_put(clk);
+		return ret;
+	}
+
+	ipa_ctx->core_clock = clk;
+
+	return 0;
+}
+
+static void ipa_clock_exit(void)
+{
+	clk_put(ipa_ctx->core_clock);
+	ipa_ctx->core_clock = NULL;
+}
+
+/**
+ * ipa_enable_clks() - Turn on IPA clocks
+ */
+static void ipa_enable_clks(void)
+{
+	if (WARN_ON(ipa_interconnect_enable()))
+		return;
+
+	if (WARN_ON(clk_prepare_enable(ipa_ctx->core_clock)))
+		ipa_interconnect_disable();
+}
+
+/**
+ * ipa_disable_clks() - Turn off IPA clocks
+ */
+static void ipa_disable_clks(void)
+{
+	clk_disable_unprepare(ipa_ctx->core_clock);
+	WARN_ON(ipa_interconnect_disable());
+}
+
+/* Add an IPA client under protection of the mutex.  This is called
+ * for the first client, but a race could mean another caller gets
+ * the first reference.  When the first reference is taken, IPA
+ * clocks are enabled endpoints are resumed.  A positive reference count
+ * means the endpoints are active; this doesn't set the first reference
+ * until after this is complete (and the mutex, not the atomic
+ * count, is what protects this).
+ */
+static void ipa_client_add_first(void)
+{
+	mutex_lock(&ipa_ctx->active_clients_mutex);
+
+	/* A reference might have been added while awaiting the mutex. */
+	if (!atomic_inc_not_zero(&ipa_ctx->active_clients_count)) {
+		ipa_enable_clks();
+		ipa_ep_resume_all();
+		atomic_inc(&ipa_ctx->active_clients_count);
+	} else {
+		ipa_assert(atomic_read(&ipa_ctx->active_clients_count) > 1);
+	}
+
+	mutex_unlock(&ipa_ctx->active_clients_mutex);
+}
+
+/* Attempt to add an IPA client reference, but only if this does not
+ * represent the initiaal reference.  Returns true if the reference
+ * was taken, false otherwise.
+ */
+static bool ipa_client_add_not_first(void)
+{
+	return !!atomic_inc_not_zero(&ipa_ctx->active_clients_count);
+}
+
+/* Add an IPA client, but only if the reference count is already
+ * non-zero.  (This is used to avoid blocking.)  Returns true if the
+ * additional reference was added successfully, or false otherwise.
+ */
+bool ipa_client_add_additional(void)
+{
+	return ipa_client_add_not_first();
+}
+
+/* Add an IPA client.  If this is not the first client, the
+ * reference count is updated and return is immediate.  Otherwise
+ * ipa_client_add_first() will safely add the first client, enabling
+ * clocks and setting up (resuming) endpoints before returning.
+ */
+void ipa_client_add(void)
+{
+	/* There's nothing more to do if this isn't the first reference */
+	if (!ipa_client_add_not_first())
+		ipa_client_add_first();
+}
+
+/* Remove an IPA client under protection of the mutex.  This is
+ * called for the last remaining client, but a race could mean
+ * another caller gets an additional reference before the mutex
+ * is acquired.  When the final reference is dropped, endpoints are
+ * suspended and IPA clocks disabled.
+ */
+static void ipa_client_remove_final(void)
+{
+	mutex_lock(&ipa_ctx->active_clients_mutex);
+
+	/* A reference might have been removed while awaiting the mutex. */
+	if (!atomic_dec_return(&ipa_ctx->active_clients_count)) {
+		ipa_ep_suspend_all();
+		ipa_disable_clks();
+	}
+
+	mutex_unlock(&ipa_ctx->active_clients_mutex);
+}
+
+/* Decrement the active clients reference count, and if the result
+ * is 0, suspend the endpoints and disable clocks.
+ *
+ * This function runs in work queue context, scheduled to run whenever
+ * the last reference would be dropped in ipa_client_remove().
+ */
+static void ipa_client_remove_deferred(struct work_struct *work)
+{
+	ipa_client_remove_final();
+}
+
+/* Attempt to remove a client reference, but only if this is not the
+ * only reference remaining.  Returns true if the reference was
+ * removed, or false if doing so would produce a zero reference
+ * count.
+ */
+static bool ipa_client_remove_not_final(void)
+{
+	return !!atomic_add_unless(&ipa_ctx->active_clients_count, -1, 1);
+}
+
+/* Attempt to remove an IPA client reference.  If this represents
+ * the last reference arrange for ipa_client_remove_final() to be
+ * called in workqueue context, dropping the last reference under
+ * protection of the mutex.
+ */
+void ipa_client_remove(void)
+{
+	if (!ipa_client_remove_not_final())
+		queue_work(ipa_ctx->power_mgmt_wq, &ipa_client_remove_work);
+}
+
+/** ipa_inc_acquire_wakelock() - Increase active clients counter, and
+ * acquire wakelock if necessary
+ */
+void ipa_inc_acquire_wakelock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipa_ctx->wakeup_lock, flags);
+
+	ipa_ctx->wakeup_count++;
+	if (ipa_ctx->wakeup_count == 1)
+		__pm_stay_awake(&ipa_ctx->wakeup);
+
+	spin_unlock_irqrestore(&ipa_ctx->wakeup_lock, flags);
+}
+
+/** ipa_dec_release_wakelock() - Decrease active clients counter
+ *
+ * In case if the ref count is 0, release the wakelock.
+ */
+void ipa_dec_release_wakelock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipa_ctx->wakeup_lock, flags);
+
+	ipa_ctx->wakeup_count--;
+	if (ipa_ctx->wakeup_count == 0)
+		__pm_relax(&ipa_ctx->wakeup);
+
+	spin_unlock_irqrestore(&ipa_ctx->wakeup_lock, flags);
+}
+
+/** ipa_suspend_handler() - Handle the suspend interrupt
+ * @interrupt:	Interrupt type
+ * @endpoints:	Interrupt specific information data
+ */
+static void ipa_suspend_handler(enum ipa_irq_type interrupt, u32 interrupt_data)
+{
+	u32 endpoints = interrupt_data;
+
+	while (endpoints) {
+		enum ipa_client_type client;
+		u32 i = __ffs(endpoints);
+
+		endpoints ^= BIT(i);
+
+		if (!ipa_ctx->ep[i].allocated)
+			continue;
+
+		client = ipa_ctx->ep[i].client;
+		if (!ipa_ap_consumer(client))
+			continue;
+
+		/* endpoint will be unsuspended by enabling IPA clocks */
+		mutex_lock(&ipa_ctx->transport_pm.transport_pm_mutex);
+		if (!atomic_read(&ipa_ctx->transport_pm.dec_clients)) {
+			ipa_client_add();
+
+			atomic_set(&ipa_ctx->transport_pm.dec_clients, 1);
+		}
+		mutex_unlock(&ipa_ctx->transport_pm.transport_pm_mutex);
+	}
+}
+
+/**
+ * ipa_init_interrupts() - Initialize IPA interrupts
+ */
+static int ipa_init_interrupts(void)
+{
+	int ret;
+
+	ret = ipa_interrupts_init();
+	if (!ret)
+		return ret;
+
+	ipa_add_interrupt_handler(IPA_TX_SUSPEND_IRQ, ipa_suspend_handler);
+
+	return 0;
+}
+
+static void ipa_freeze_clock_vote_and_notify_modem(void)
+{
+	u32 value;
+	u32 mask;
+
+	if (ipa_ctx->smp2p_info.res_sent)
+		return;
+
+	if (!ipa_ctx->smp2p_info.enabled_state) {
+		ipa_err("smp2p out gpio not assigned\n");
+		return;
+	}
+
+	ipa_ctx->smp2p_info.ipa_clk_on = ipa_client_add_additional();
+
+	/* Signal whether the clock is enabled */
+	mask = BIT(ipa_ctx->smp2p_info.enabled_bit);
+	value = ipa_ctx->smp2p_info.ipa_clk_on ? mask : 0;
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.enabled_state, mask,
+				    value);
+
+	/* Now indicate that the enabled flag is valid */
+	mask = BIT(ipa_ctx->smp2p_info.valid_bit);
+	value = mask;
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.valid_state, mask,
+				    value);
+
+	ipa_ctx->smp2p_info.res_sent = true;
+}
+
+void ipa_reset_freeze_vote(void)
+{
+	u32 mask;
+
+	if (!ipa_ctx->smp2p_info.res_sent)
+		return;
+
+	if (ipa_ctx->smp2p_info.ipa_clk_on)
+		ipa_client_remove();
+
+	/* Reset the clock enabled valid flag */
+	mask = BIT(ipa_ctx->smp2p_info.valid_bit);
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.valid_state, mask, 0);
+
+	/* Mark the clock disabled for good measure... */
+	mask = BIT(ipa_ctx->smp2p_info.enabled_bit);
+	qcom_smem_state_update_bits(ipa_ctx->smp2p_info.enabled_state, mask, 0);
+
+	ipa_ctx->smp2p_info.res_sent = false;
+	ipa_ctx->smp2p_info.ipa_clk_on = false;
+}
+
+static int
+ipa_panic_notifier(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	ipa_freeze_clock_vote_and_notify_modem();
+	ipa_uc_panic_notifier();
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ipa_panic_blk = {
+	.notifier_call = ipa_panic_notifier,
+	/* IPA panic handler needs to run before modem shuts down */
+	.priority = INT_MAX,
+};
+
+static void ipa_register_panic_hdlr(void)
+{
+	atomic_notifier_chain_register(&panic_notifier_list, &ipa_panic_blk);
+}
+
+/* Remoteproc callbacks for SSR events: prepare, start, stop, unprepare */
+int ipa_ssr_prepare(struct rproc_subdev *subdev)
+{
+	printk("======== SSR prepare received ========\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_prepare);
+
+int ipa_ssr_start(struct rproc_subdev *subdev)
+{
+	printk("======== SSR start received ========\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_start);
+
+void ipa_ssr_stop(struct rproc_subdev *subdev, bool crashed)
+{
+	printk("======== SSR stop received ========\n");
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_stop);
+
+void ipa_ssr_unprepare(struct rproc_subdev *subdev)
+{
+	printk("======== SSR unprepare received ========\n");
+}
+EXPORT_SYMBOL_GPL(ipa_ssr_unprepare);
+
+/**
+ * ipa_post_init() - Initialize the IPA Driver (Part II).
+ *
+ * Perform initialization that requires interaction with IPA hardware.
+ */
+static void ipa_post_init(void)
+{
+	int ret;
+
+	ipa_debug("ipa_post_init() started\n");
+
+	ret = gsi_device_init(ipa_ctx->gsi);
+	if (ret) {
+		ipa_err(":gsi register error - %d\n", ret);
+		return;
+	}
+
+	/* setup the AP-IPA endpoints */
+	if (ipa_ep_apps_setup()) {
+		ipa_err(":failed to setup IPA-Apps endpoints\n");
+		gsi_device_exit(ipa_ctx->gsi);
+
+		return;
+	}
+
+	ipa_ctx->uc_ctx = ipa_uc_init(ipa_ctx->ipa_phys);
+	if (!ipa_ctx->uc_ctx)
+		ipa_err("microcontroller init failed\n");
+
+	ipa_register_panic_hdlr();
+
+	ipa_ctx->modem_clk_vote_valid = true;
+
+	if (ipa_wwan_init())
+		ipa_err("WWAN init failed (ignoring)\n");
+
+	dev_info(ipa_ctx->dev, "IPA driver initialization was successful.\n");
+}
+
+/** ipa_pre_init() - Initialize the IPA Driver.
+ *
+ * Perform initialization which doesn't require access to IPA hardware.
+ */
+static int ipa_pre_init(void)
+{
+	int ret = 0;
+
+	/* enable IPA clocks explicitly to allow the initialization */
+	ipa_enable_clks();
+
+	ipa_init_hw();
+
+	ipa_ctx->ep_count = ipa_get_ep_count();
+	ipa_debug("ep_count %u\n", ipa_get_ep_count());
+	ipa_assert(ipa_ctx->ep_count <= IPA_EP_COUNT_MAX);
+
+	ipa_sram_settings_read();
+	if (ipa_ctx->smem_size < IPA_MEM_END_OFST) {
+		ipa_err("insufficient memory: %hu bytes available, need %u\n",
+			ipa_ctx->smem_size, IPA_MEM_END_OFST);
+		ret = -ENOMEM;
+		goto err_disable_clks;
+	}
+
+	mutex_init(&ipa_ctx->active_clients_mutex);
+	atomic_set(&ipa_ctx->active_clients_count, 1);
+
+	/* Create workqueues for power management */
+	ipa_ctx->power_mgmt_wq =
+		create_singlethread_workqueue("ipa_power_mgmt");
+	if (!ipa_ctx->power_mgmt_wq) {
+		ipa_err("failed to create power mgmt wq\n");
+		ret = -ENOMEM;
+		goto err_disable_clks;
+	}
+
+	mutex_init(&ipa_ctx->transport_pm.transport_pm_mutex);
+
+	/* init the lookaside cache */
+
+	ipa_ctx->dp = ipa_dp_init();
+	if (!ipa_ctx->dp)
+		goto err_destroy_pm_wq;
+
+	/* allocate memory for DMA_TASK workaround */
+	ret = ipa_gsi_dma_task_alloc();
+	if (ret)
+		goto err_dp_exit;
+
+	/* Create a wakeup source. */
+	wakeup_source_init(&ipa_ctx->wakeup, "IPA_WS");
+	spin_lock_init(&ipa_ctx->wakeup_lock);
+
+	/* Note enabling dynamic clock division must not be
+	 * attempted for IPA hardware versions prior to 3.5.
+	 */
+	ipa_enable_dcd();
+
+	/* Assign resource limitation to each group */
+	ipa_set_resource_groups_min_max_limits();
+
+	ret = ipa_init_interrupts();
+	if (!ret)
+		return 0;	/* Success! */
+
+	ipa_err("ipa initialization of interrupts failed\n");
+err_dp_exit:
+	ipa_dp_exit(ipa_ctx->dp);
+	ipa_ctx->dp = NULL;
+err_destroy_pm_wq:
+	destroy_workqueue(ipa_ctx->power_mgmt_wq);
+err_disable_clks:
+	ipa_disable_clks();
+
+	return ret;
+}
+
+static int ipa_firmware_load(struct device *dev)
+{
+	const struct firmware *fw;
+	struct device_node *node;
+	struct resource res;
+	phys_addr_t phys;
+	ssize_t size;
+	void *virt;
+	int ret;
+
+	ret = request_firmware(&fw, IPA_FWS_PATH, dev);
+	if (ret)
+		return ret;
+
+	node = of_parse_phandle(dev->of_node, "memory-region", 0);
+	if (!node) {
+		dev_err(dev, "memory-region not specified\n");
+		ret = -EINVAL;
+		goto out_release_firmware;
+	}
+
+	ret = of_address_to_resource(node, 0, &res);
+	if (ret)
+		goto out_release_firmware;
+
+	phys = res.start,
+	size = (size_t)resource_size(&res);
+	virt = memremap(phys, size, MEMREMAP_WC);
+	if (!virt) {
+		ret = -ENOMEM;
+		goto out_release_firmware;
+	}
+
+	ret = qcom_mdt_load(dev, fw, IPA_FWS_PATH, IPA_PAS_ID,
+			    virt, phys, size, NULL);
+	if (!ret)
+		ret = qcom_scm_pas_auth_and_reset(IPA_PAS_ID);
+
+	memunmap(virt);
+out_release_firmware:
+	release_firmware(fw);
+
+	return ret;
+}
+
+/* Threaded IRQ handler for modem "ipa-clock-query" SMP2P interrupt */
+static irqreturn_t ipa_smp2p_modem_clk_query_isr(int irq, void *ctxt)
+{
+	ipa_freeze_clock_vote_and_notify_modem();
+
+	return IRQ_HANDLED;
+}
+
+/* Threaded IRQ handler for modem "ipa-post-init" SMP2P interrupt */
+static irqreturn_t ipa_smp2p_modem_post_init_isr(int irq, void *ctxt)
+{
+	ipa_post_init();
+
+	return IRQ_HANDLED;
+}
+
+static int
+ipa_smp2p_irq_init(struct device *dev, const char *name, irq_handler_t handler)
+{
+	struct device_node *node = dev->of_node;
+	unsigned int irq;
+	int ret;
+
+	ret = of_irq_get_byname(node, name);
+	if (ret < 0)
+		return ret;
+	if (!ret)
+		return -EINVAL;		/* IRQ mapping failure */
+	irq = ret;
+
+	ret = devm_request_threaded_irq(dev, irq, NULL, handler, 0, name, dev);
+	if (ret)
+		return ret;
+
+	return irq;
+}
+
+static void
+ipa_smp2p_irq_exit(struct device *dev, unsigned int irq)
+{
+	devm_free_irq(dev, irq, dev);
+}
+
+static int ipa_smp2p_init(struct device *dev, bool modem_init)
+{
+	struct qcom_smem_state *enabled_state;
+	struct qcom_smem_state *valid_state;
+	struct device_node *node;
+	unsigned int enabled_bit;
+	unsigned int valid_bit;
+	unsigned int clock_irq;
+	int ret;
+
+	node = dev->of_node;
+
+	valid_state = qcom_smem_state_get(dev, "ipa-clock-enabled-valid",
+					  &valid_bit);
+	if (IS_ERR(valid_state))
+		return PTR_ERR(valid_state);
+
+	enabled_state = qcom_smem_state_get(dev, "ipa-clock-enabled",
+					    &enabled_bit);
+	if (IS_ERR(enabled_state)) {
+		ret = PTR_ERR(enabled_state);
+		ipa_err("error %d getting ipa-clock-enabled state\n", ret);
+
+		return ret;
+	}
+
+	ret = ipa_smp2p_irq_init(dev, "ipa-clock-query",
+				 ipa_smp2p_modem_clk_query_isr);
+	if (ret < 0)
+		return ret;
+	clock_irq = ret;
+
+	if (modem_init) {
+		/* Result will be non-zero (negative for error) */
+		ret = ipa_smp2p_irq_init(dev, "ipa-post-init",
+					 ipa_smp2p_modem_post_init_isr);
+		if (ret < 0) {
+			ipa_smp2p_irq_exit(dev, clock_irq);
+
+			return ret;
+		}
+	}
+
+	/* Success.  Record our smp2p information */
+	ipa_ctx->smp2p_info.valid_state = valid_state;
+	ipa_ctx->smp2p_info.valid_bit = valid_bit;
+	ipa_ctx->smp2p_info.enabled_state = enabled_state;
+	ipa_ctx->smp2p_info.enabled_bit = enabled_bit;
+	ipa_ctx->smp2p_info.clock_query_irq = clock_irq;
+	ipa_ctx->smp2p_info.post_init_irq = modem_init ? ret : 0;
+
+	return 0;
+}
+
+static void ipa_smp2p_exit(struct device *dev)
+{
+	if (ipa_ctx->smp2p_info.post_init_irq)
+		ipa_smp2p_irq_exit(dev, ipa_ctx->smp2p_info.post_init_irq);
+	ipa_smp2p_irq_exit(dev, ipa_ctx->smp2p_info.clock_query_irq);
+
+	memset(&ipa_ctx->smp2p_info, 0, sizeof(ipa_ctx->smp2p_info));
+}
+
+static const struct ipa_match_data tz_init = {
+	.init_type = ipa_tz_init,
+};
+
+static const struct ipa_match_data modem_init = {
+	.init_type = ipa_modem_init,
+};
+
+static const struct of_device_id ipa_plat_drv_match[] = {
+	{
+		.compatible = "qcom,ipa-sdm845-tz_init",
+		.data = &tz_init,
+	},
+	{
+		.compatible = "qcom,ipa-sdm845-modem_init",
+		.data = &modem_init,
+	},
+	{}
+};
+
+static int ipa_plat_drv_probe(struct platform_device *pdev)
+{
+	const struct ipa_match_data *match_data;
+	struct resource *res;
+	struct device *dev;
+	bool modem_init;
+	int ret;
+
+	/* We assume we're working on 64-bit hardware */
+	BUILD_BUG_ON(!IS_ENABLED(CONFIG_64BIT));
+
+	dev = &pdev->dev;
+
+	match_data = of_device_get_match_data(dev);
+	modem_init = match_data->init_type == ipa_modem_init;
+
+	/* If we need Trust Zone, make sure it's ready */
+	if (!modem_init)
+		if (!qcom_scm_is_available())
+			return -EPROBE_DEFER;
+
+	/* Initialize the smp2p driver early.  It might not be ready
+	 * when we're probed, so it might return -EPROBE_DEFER.
+	 */
+	ret = ipa_smp2p_init(dev, modem_init);
+	if (ret)
+		return ret;
+
+	/* Initialize the interconnect driver early too.  It might
+	 * also return -EPROBE_DEFER.
+	 */
+	ret = ipa_interconnect_init(dev);
+	if (ret)
+		goto out_smp2p_exit;
+
+	ret = ipa_clock_init(dev);
+	if (ret)
+		goto err_interconnect_exit;
+
+	ipa_ctx->dev = dev;	/* Set early for ipa_err()/ipa_debug() */
+
+	/* Compute a bitmask representing which endpoints support filtering */
+	ipa_ctx->filter_bitmap = ipa_filter_bitmap_init();
+	ipa_debug("filter_bitmap 0x%08x\n", ipa_ctx->filter_bitmap);
+	if (!ipa_ctx->filter_bitmap)
+		goto err_clock_exit;
+
+	ret = platform_get_irq_byname(pdev, "ipa");
+	if (ret < 0)
+		goto err_clear_filter_bitmap;
+	ipa_ctx->ipa_irq = ret;
+
+	/* Get IPA memory range */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ipa");
+	if (!res) {
+		ret = -ENODEV;
+		goto err_clear_ipa_irq;
+	}
+
+	/* Setup IPA register access */
+	ret = ipa_reg_init(res->start, (size_t)resource_size(res));
+	if (ret)
+		goto err_clear_ipa_irq;
+	ipa_ctx->ipa_phys = res->start;
+
+	ipa_ctx->gsi = gsi_init(pdev);
+	if (IS_ERR(ipa_ctx->gsi)) {
+		ret = PTR_ERR(ipa_ctx->gsi);
+		goto err_clear_gsi;
+	}
+
+	ret = ipa_dma_init(dev, IPA_HW_TBL_SYSADDR_ALIGN);
+	if (ret)
+		goto err_clear_gsi;
+
+	ret = ipahal_init();
+	if (ret)
+		goto err_dma_exit;
+
+	ipa_ctx->cmd_prod_ep_id = IPA_EP_ID_BAD;
+	ipa_ctx->lan_cons_ep_id = IPA_EP_ID_BAD;
+
+	/* Proceed to real initialization */
+	ret = ipa_pre_init();
+	if (ret)
+		goto err_clear_dev;
+
+	/* If the modem is not verifying and loading firmware we need to
+	 * get it loaded ourselves.  Only then can we proceed with the
+	 * second stage of IPA initialization.  If the modem is doing it,
+	 * it will send an SMP2P interrupt to signal this has been done,
+	 * and that will trigger the "post init".
+	 */
+	if (!modem_init) {
+		ret = ipa_firmware_load(dev);
+		if (ret)
+			goto err_clear_dev;
+
+		/* Now we can proceed to stage two initialization */
+		ipa_post_init();
+	}
+
+	return 0;	/* Success */
+
+err_clear_dev:
+	ipa_ctx->lan_cons_ep_id = 0;
+	ipa_ctx->cmd_prod_ep_id = 0;
+	ipahal_exit();
+err_dma_exit:
+	ipa_dma_exit();
+err_clear_gsi:
+	ipa_ctx->gsi = NULL;
+	ipa_ctx->ipa_phys = 0;
+	ipa_reg_exit();
+err_clear_ipa_irq:
+	ipa_ctx->ipa_irq = 0;
+err_clear_filter_bitmap:
+	ipa_ctx->filter_bitmap = 0;
+err_interconnect_exit:
+	ipa_interconnect_exit();
+err_clock_exit:
+	ipa_clock_exit();
+	ipa_ctx->dev = NULL;
+out_smp2p_exit:
+	ipa_smp2p_exit(dev);
+
+	return ret;
+}
+
+static int ipa_plat_drv_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	ipa_ctx->dev = NULL;
+	ipahal_exit();
+	ipa_dma_exit();
+	ipa_ctx->gsi = NULL;	/* XXX ipa_gsi_exit() */
+	ipa_reg_exit();
+
+	ipa_ctx->ipa_phys = 0;
+
+	if (ipa_ctx->lan_cons_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_free(ipa_ctx->lan_cons_ep_id);
+		ipa_ctx->lan_cons_ep_id = IPA_EP_ID_BAD;
+	}
+	if (ipa_ctx->cmd_prod_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_free(ipa_ctx->cmd_prod_ep_id);
+		ipa_ctx->cmd_prod_ep_id = IPA_EP_ID_BAD;
+	}
+	ipa_ctx->ipa_irq = 0;	/* XXX Need to de-initialize? */
+	ipa_ctx->filter_bitmap = 0;
+	ipa_interconnect_exit();
+	ipa_smp2p_exit(dev);
+
+	return 0;
+}
+
+/**
+ * ipa_ap_suspend() - suspend callback for runtime_pm
+ * @dev:	IPA device structure
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP suspend
+ * operation is invoked, usually by pressing a suspend button.
+ *
+ * Return: 	0 if successful, -EAGAIN if IPA is in use
+ */
+int ipa_ap_suspend(struct device *dev)
+{
+	u32 i;
+
+	/* In case there is a tx/rx handler in polling mode fail to suspend */
+	for (i = 0; i < ipa_ctx->ep_count; i++) {
+		if (ipa_ctx->ep[i].sys && ipa_ep_polling(&ipa_ctx->ep[i])) {
+			ipa_err("EP %d is in polling state, do not suspend\n",
+				i);
+			return -EAGAIN;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * ipa_ap_resume() - resume callback for runtime_pm
+ * @dev:	IPA device structure
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP resume
+ * operation is invoked.
+ *
+ * Return:	Zero
+ */
+int ipa_ap_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops ipa_pm_ops = {
+	.suspend_noirq = ipa_ap_suspend,
+	.resume_noirq = ipa_ap_resume,
+};
+
+static struct platform_driver ipa_plat_drv = {
+	.probe = ipa_plat_drv_probe,
+	.remove = ipa_plat_drv_remove,
+	.driver = {
+		.name = "ipa",
+		.owner = THIS_MODULE,
+		.pm = &ipa_pm_ops,
+		.of_match_table = ipa_plat_drv_match,
+	},
+};
+
+builtin_platform_driver(ipa_plat_drv);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("IPA HW device driver");
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 10/12] soc: qcom: ipa: data path
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This patch contains "ipa_dp.c", which includes the bulk of the data
path code.  There is an overview in the code of how things operate,
but there are already plans to rework this portion of the driver.

In particular:
  - Interrupt handling will be replaced with a threaded interrupt
    handler.  Currently handling occurs in a combination of
    interrupt and workqueue context, and this requires locking
    and atomic operations for proper synchronization.
  - Currently, only receive endpoints use NAPI.  Transmit
    completion interrupts are disabled, and are handled in batches
    by periodically scheduling an interrupting no-op request.
    The plan is to arrange for transmit requests to generate
    interrupts, and their completion will be processed with other
    completions in the NAPI poll function.  This will also allow
    accurate feedback about packet sojourn time to be provided to
    queue limiting mechanisms.
  - Not all receive endpoints use NAPI.  The plan is for *all*
    endpoints to use NAPI.  And because all endpoints share a
    common GSI interrupt, a single NAPI structure will used to
    managing the processing for all completions on all endpoints.
  - Receive buffers are posted to the hardware by a workqueue
    function.  Instead, the plan is to have this done by the
    NAPI poll routine.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_dp.c | 1994 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 1994 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_dp.c

diff --git a/drivers/net/ipa/ipa_dp.c b/drivers/net/ipa/ipa_dp.c
new file mode 100644
index 000000000000..c16ac74765b8
--- /dev/null
+++ b/drivers/net/ipa/ipa_dp.c
@@ -0,0 +1,1994 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+
+#include "ipa_i.h"	/* ipa_err() */
+#include "ipahal.h"
+#include "ipa_dma.h"
+
+/**
+ * DOC:  The IPA Data Path
+ *
+ * The IPA is used to transmit data between execution environments.
+ * The data path code uses functions and structures supplied by the
+ * GSI to interact with the IPA hardware.  A packet to be transmitted
+ * or received is held in a socket buffer.  Each has a "wrapper"
+ * structure associated with it.  A GSI transfer request refers to
+ * the packet wrapper, and when queued to the hardware the packet
+ * wrapper is added to a list of outstanding requests for an endpoint
+ * (maintained in the head_desc_list in the endpoint's system context).
+ * When the GSI transfer completes, a callback function is provided
+ * the packet wrapper pointer, allowing it to be released after the
+ * received socket buffer has been passed up the stack, or a buffer
+ * whose data has been transmitted has been freed.
+ *
+ * Producer (PROD) endpoints are used to send data from the AP toward
+ * the IPA.  The common function for sending data on producer endpoints
+ * is ipa_send().  It takes a system context and an array of IPA
+ * descriptors as arguments.  Each descriptor is given a TX packet
+ * wrapper, and its content is translated into an equivalent GSI
+ * transfer element  structure after its memory address is mapped for
+ * DMA.  The GSI transfer element array is finally passed to the GSI
+ * layer using gsi_channel_queue().
+ *
+ * The code provides a "no_intr" feature, allowing endpoints to have
+ * their transmit completions not produce an interrupt.  (This
+ * behavior is used only for the modem producer.)  In this case, a
+ * no-op request is generated every 200 milliseconds while transmit
+ * requests are outstanding.  The no-op will generate an interrupt
+ * when it's complete, and its completion implies the completion of
+ * all transmit requests issued before it.  The GSI will call
+ * ipa_gsi_irq_tx_notify_cb() in response to interrupts on a producer
+ * endpoint.
+ *
+ * Receive buffers are passed to consumer (CONS) channels to be
+ * available to hold incoming data.  Arriving data is placed
+ * in these buffers, leading to events being generated on the event
+ * ring assciated with a channel.  When an interrupt occurs on a
+ * consumer endpoint, the GSI layer calls ipa_gsi_irq_rx_notify_cb().
+ * This causes the endpoint to switch to polling mode.  The
+ * completion of a receive also leads to ipa_replenish_rx_cache()
+ * being called, to replace the consumed buffer.
+ *
+ * Consumer enpoints optionally use NAPI (only the modem consumer,
+ * WWAN_CONS, does currently).  An atomic variable records whether
+ * the endpoint is in polling mode or not.  This is needed because
+ * switching to polling mode is currently done in a workqueue.  Once
+ * NAPI polling completes, and endpoint switches back to interrupt
+ * mode.
+ */
+
+/**
+ * struct ipa_tx_pkt_wrapper - IPA transmit packet wrapper
+ * @type:	type of descriptor
+ * @sys:	Corresponding IPA sys context
+ * @mem:	Memory buffer used by this packet
+ * @callback:	IPA client provided callback
+ * @user1:	Cookie1 for above callback
+ * @user2:	Cookie2 for above callback
+ * @link:	Links for the endpoint's sys->head_desc_list
+ * @cnt:	Number of descriptors in request
+ * @done_work:	Work structure used when complete
+ */
+struct ipa_tx_pkt_wrapper {
+	enum ipa_desc_type type;
+	struct ipa_sys_context *sys;
+	struct ipa_dma_mem mem;
+	void (*callback)(void *user1, int user2);
+	void *user1;
+	int user2;
+	struct list_head link;
+	u32 cnt;
+	struct work_struct done_work;
+};
+
+/** struct ipa_rx_pkt_wrapper - IPA Rx packet wrapper
+ * @link:	Links for the endpoint's sys->head_desc_list
+ * @skb:	Socket buffer containing the received packet
+ * @len:	How many bytes are copied into skb's buffer
+ */
+struct ipa_rx_pkt_wrapper {
+	struct list_head link;
+	struct sk_buff *skb;
+	dma_addr_t dma_addr;
+};
+
+/** struct ipa_sys_context - IPA GPI endpoint context
+ * @len:	The number of entries in @head_desc_list
+ * @tx:		Details related to AP->IPA endpoints
+ * @rx:		Details related to IPA->AP endpoints
+ * @ep:		Associated endpoint
+ * @head_desc_list: List of packets
+ * @spinlock:	Lock protecting the descriptor list
+ * @workqueue:	Workqueue used for this endpoint
+ */
+struct ipa_sys_context {
+	u32 len;
+	union {
+		struct {	/* Consumer endpoints only */
+			u32 len_pending_xfer;
+			atomic_t curr_polling_state;
+			struct delayed_work switch_to_intr_work; /* sys->wq */
+			void (*pyld_hdlr)(struct sk_buff *,
+					  struct ipa_sys_context *);
+			u32 buff_sz;
+			u32 pool_sz;
+			struct sk_buff *prev_skb;
+			unsigned int len_rem;
+			unsigned int len_pad;		/* APPS_LAN only */
+			unsigned int len_partial;	/* APPS_LAN only */
+			bool drop_packet;		/* APPS_LAN only */
+
+			struct work_struct work; /* sys->wq */
+			struct delayed_work replenish_work; /* sys->wq */
+		} rx;
+		struct {	/* Producer endpoints only */
+			/* no_intr/nop is APPS_WAN_PROD only */
+			bool no_intr;
+			atomic_t nop_pending;
+			struct hrtimer nop_timer;
+			struct work_struct nop_work; /* sys->wq */
+		} tx;
+	};
+
+	/* ordering is important - mutable fields go above */
+	struct ipa_ep_context *ep;
+	struct list_head head_desc_list; /* contains len entries */
+	spinlock_t spinlock;		/* protects head_desc list */
+	struct workqueue_struct *wq;
+	/* ordering is important - other immutable fields go below */
+};
+
+/**
+ * struct ipa_dp - IPA data path information
+ * @tx_pkt_wrapper_cache:	Tx packets cache
+ * @rx_pkt_wrapper_cache:	Rx packets cache
+ */
+struct ipa_dp {
+	struct kmem_cache *tx_pkt_wrapper_cache;
+	struct kmem_cache *rx_pkt_wrapper_cache;
+};
+
+/**
+ * struct ipa_tag_completion - Reference counted completion object
+ * @comp:	Completion when last reference is dropped
+ * @cnt:	Reference count
+ */
+struct ipa_tag_completion {
+	struct completion comp;
+	atomic_t cnt;
+};
+
+#define CHANNEL_RESET_AGGR_RETRY_COUNT	3
+#define CHANNEL_RESET_DELAY		1	/* milliseconds */
+
+#define IPA_QMAP_HEADER_LENGTH		4
+
+#define IPA_WAN_AGGR_PKT_CNT		5
+#define POLLING_INACTIVITY_RX		40
+#define POLLING_MIN_SLEEP_RX		1010	/* microseconds */
+#define POLLING_MAX_SLEEP_RX		1050	/* microseconds */
+
+#define IPA_RX_BUFFER_ORDER	1	/* Default RX buffer is 2^1 pages */
+#define IPA_RX_BUFFER_SIZE	(1 << (IPA_RX_BUFFER_ORDER + PAGE_SHIFT))
+
+/* The amount of RX buffer space consumed by standard skb overhead */
+#define IPA_RX_BUFFER_RESERVED \
+	(IPA_RX_BUFFER_SIZE - SKB_MAX_ORDER(NET_SKB_PAD, IPA_RX_BUFFER_ORDER))
+
+/* RX buffer space remaining after standard overhead is consumed */
+#define IPA_RX_BUFFER_AVAILABLE(X)	((X) - IPA_RX_BUFFER_RESERVED)
+
+#define IPA_RX_BUFF_CLIENT_HEADROOM	256
+
+#define IPA_SIZE_DL_CSUM_META_TRAILER	8
+
+#define IPA_REPL_XFER_THRESH		10
+
+/* How long before sending an interrupting no-op to handle TX completions */
+#define IPA_TX_NOP_DELAY_NS		(2 * 1000 * 1000)	/* 2 msec */
+
+static void ipa_rx_switch_to_intr_mode(struct ipa_sys_context *sys);
+
+static void ipa_replenish_rx_cache(struct ipa_sys_context *sys);
+static void ipa_replenish_rx_work_func(struct work_struct *work);
+static void ipa_wq_handle_rx(struct work_struct *work);
+static void ipa_rx_common(struct ipa_sys_context *sys, u32 size);
+static void ipa_cleanup_rx(struct ipa_sys_context *sys);
+static int ipa_poll_gsi_pkt(struct ipa_sys_context *sys);
+
+static void ipa_tx_complete(struct ipa_tx_pkt_wrapper *tx_pkt)
+{
+	struct device *dev = ipa_ctx->dev;
+
+	/* If DMA memory was mapped, unmap it */
+	if (tx_pkt->mem.virt) {
+		if (tx_pkt->type == IPA_DATA_DESC_SKB_PAGED)
+			dma_unmap_page(dev, tx_pkt->mem.phys,
+				       tx_pkt->mem.size, DMA_TO_DEVICE);
+		else
+			dma_unmap_single(dev, tx_pkt->mem.phys,
+					 tx_pkt->mem.size, DMA_TO_DEVICE);
+	}
+
+	if (tx_pkt->callback)
+		tx_pkt->callback(tx_pkt->user1, tx_pkt->user2);
+
+	kmem_cache_free(ipa_ctx->dp->tx_pkt_wrapper_cache, tx_pkt);
+}
+
+static void
+ipa_wq_write_done_common(struct ipa_sys_context *sys,
+			 struct ipa_tx_pkt_wrapper *tx_pkt)
+{
+	struct ipa_tx_pkt_wrapper *next_pkt;
+	int cnt;
+	int i;
+
+	cnt = tx_pkt->cnt;
+	for (i = 0; i < cnt; i++) {
+		ipa_assert(!list_empty(&sys->head_desc_list));
+
+		spin_lock_bh(&sys->spinlock);
+
+		next_pkt = list_next_entry(tx_pkt, link);
+		list_del(&tx_pkt->link);
+		sys->len--;
+
+		spin_unlock_bh(&sys->spinlock);
+
+		ipa_tx_complete(tx_pkt);
+
+		tx_pkt = next_pkt;
+	}
+}
+
+/**
+ * ipa_wq_write_done() - Work function executed when TX completes
+ * * @done_work:	work_struct used by the work queue
+ */
+static void ipa_wq_write_done(struct work_struct *done_work)
+{
+	struct ipa_tx_pkt_wrapper *this_pkt;
+	struct ipa_tx_pkt_wrapper *tx_pkt;
+	struct ipa_sys_context *sys;
+
+	tx_pkt = container_of(done_work, struct ipa_tx_pkt_wrapper, done_work);
+	sys = tx_pkt->sys;
+	spin_lock_bh(&sys->spinlock);
+	this_pkt = list_first_entry(&sys->head_desc_list,
+				    struct ipa_tx_pkt_wrapper, link);
+	while (tx_pkt != this_pkt) {
+		spin_unlock_bh(&sys->spinlock);
+		ipa_wq_write_done_common(sys, this_pkt);
+		spin_lock_bh(&sys->spinlock);
+		this_pkt = list_first_entry(&sys->head_desc_list,
+					    struct ipa_tx_pkt_wrapper, link);
+	}
+	spin_unlock_bh(&sys->spinlock);
+	ipa_wq_write_done_common(sys, tx_pkt);
+}
+
+/**
+ * ipa_rx_poll() - Poll the rx packets from IPA hardware
+ * @ep_id:	Endpoint to poll
+ * @weight:	NAPI poll weight
+ *
+ * Return:	The number of received packets.
+ */
+int ipa_rx_poll(u32 ep_id, int weight)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	static int total_cnt;
+	int cnt = 0;
+
+	while (cnt < weight && ipa_ep_polling(ep)) {
+		int ret;
+
+		ret = ipa_poll_gsi_pkt(ep->sys);
+		if (ret < 0)
+			break;
+
+		ipa_rx_common(ep->sys, (u32)ret);
+		cnt += IPA_WAN_AGGR_PKT_CNT;
+		total_cnt++;
+
+		/* Force switch back to interrupt mode if no more packets */
+		if (!ep->sys->len || total_cnt >= ep->sys->rx.pool_sz) {
+			total_cnt = 0;
+			cnt--;
+			break;
+		}
+	}
+
+	if (cnt < weight) {
+		ep->client_notify(ep->priv, IPA_CLIENT_COMP_NAPI, 0);
+		ipa_rx_switch_to_intr_mode(ep->sys);
+
+		/* Matching enable is in ipa_gsi_irq_rx_notify_cb() */
+		ipa_client_remove();
+	}
+
+	return cnt;
+}
+
+/**
+ * ipa_send_nop() - Send an interrupting no-op request to a producer endpoint.
+ * @sys:	System context for the endpoint
+ *
+ * Normally an interrupt is generated upon completion of every transfer
+ * performed by an endpoint, but a producer endpoint can be configured
+ * to avoid getting these interrupts.  Instead, once a transfer has been
+ * initiated, a no-op is scheduled to be sent after a short delay.  This
+ * no-op request will interrupt when it is complete, and in handling that
+ * interrupt, previously-completed transfers will be handled as well.  If
+ * a no-op is already scheduled, another is not initiated (there's only
+ * one pending at a time).
+ */
+static bool ipa_send_nop(struct ipa_sys_context *sys)
+{
+	struct gsi_xfer_elem nop_xfer = { };
+	struct ipa_tx_pkt_wrapper *nop_pkt;
+	u32 channel_id;
+
+	nop_pkt = kmem_cache_zalloc(ipa_ctx->dp->tx_pkt_wrapper_cache,
+				    GFP_KERNEL);
+	if (!nop_pkt)
+		return false;
+
+	nop_pkt->type = IPA_DATA_DESC;
+	/* No-op packet uses no memory for data */
+	INIT_WORK(&nop_pkt->done_work, ipa_wq_write_done);
+	nop_pkt->sys = sys;
+	nop_pkt->cnt = 1;
+
+	nop_xfer.type = GSI_XFER_ELEM_NOP;
+	nop_xfer.flags = GSI_XFER_FLAG_EOT;
+	nop_xfer.user_data = nop_pkt;
+
+	spin_lock_bh(&sys->spinlock);
+	list_add_tail(&nop_pkt->link, &sys->head_desc_list);
+	spin_unlock_bh(&sys->spinlock);
+
+	channel_id = sys->ep->channel_id;
+	if (!gsi_channel_queue(ipa_ctx->gsi, channel_id, 1, &nop_xfer, true))
+		return true;	/* Success */
+
+	spin_lock_bh(&sys->spinlock);
+	list_del(&nop_pkt->link);
+	spin_unlock_bh(&sys->spinlock);
+
+	kmem_cache_free(ipa_ctx->dp->tx_pkt_wrapper_cache, nop_pkt);
+
+	return false;
+}
+
+/**
+ * ipa_send_nop_work() - Work function for sending a no-op request
+ * nop_work:	Work structure for the request
+ *
+ * Try to send the no-op request.  If it fails, arrange to try again.
+ */
+static void ipa_send_nop_work(struct work_struct *nop_work)
+{
+	struct ipa_sys_context *sys;
+
+	sys = container_of(nop_work, struct ipa_sys_context, tx.nop_work);
+
+	/* If sending a no-op request fails, schedule another try */
+	if (!ipa_send_nop(sys))
+		queue_work(sys->wq, nop_work);
+}
+
+/**
+ * ipa_nop_timer_expiry() - Timer function to schedule a no-op request
+ * @timer:	High-resolution timer structure
+ *
+ * The delay before sending the no-op request is implemented by a
+ * high resolution timer, which will call this in interrupt context.
+ * Arrange to send the no-op in workqueue context when it expires.
+ */
+static enum hrtimer_restart ipa_nop_timer_expiry(struct hrtimer *timer)
+{
+	struct ipa_sys_context *sys;
+
+	sys = container_of(timer, struct ipa_sys_context, tx.nop_timer);
+	atomic_set(&sys->tx.nop_pending, 0);
+	queue_work(sys->wq, &sys->tx.nop_work);
+
+	return HRTIMER_NORESTART;
+}
+
+static void ipa_nop_timer_schedule(struct ipa_sys_context *sys)
+{
+	ktime_t time;
+
+	if (atomic_xchg(&sys->tx.nop_pending, 1))
+		return;
+
+	time = ktime_set(0, IPA_TX_NOP_DELAY_NS);
+	hrtimer_start(&sys->tx.nop_timer, time, HRTIMER_MODE_REL);
+}
+
+/**
+ * ipa_no_intr_init() - Configure endpoint point for no-op requests
+ * @prod_ep_id:	Endpoint that will use interrupting no-ops
+ *
+ * For some producer endpoints we don't interrupt on completions.
+ * Instead we schedule an interrupting NOP command to be issued on
+ * the endpoint after a short delay (if one is not already scheduled).
+ * When the NOP completes it signals all preceding transfers have
+ * completed also.
+ */
+void ipa_no_intr_init(u32 prod_ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[prod_ep_id];
+
+	INIT_WORK(&ep->sys->tx.nop_work, ipa_send_nop_work);
+	atomic_set(&ep->sys->tx.nop_pending, 0);
+	hrtimer_init(&ep->sys->tx.nop_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	ep->sys->tx.nop_timer.function = ipa_nop_timer_expiry;
+	ep->sys->tx.no_intr = true;
+}
+
+/**
+ * ipa_send() - Send descriptors to hardware as a single transaction
+ * @sys:	System context for endpoint
+ * @num_desc:	Number of descriptors
+ * @desc:	Transfer descriptors to send
+ *
+ * Return:	0 iff successful, or a negative error code.
+ */
+static int
+ipa_send(struct ipa_sys_context *sys, u32 num_desc, struct ipa_desc *desc)
+{
+	struct ipa_tx_pkt_wrapper *tx_pkt;
+	struct ipa_tx_pkt_wrapper *first;
+	struct ipa_tx_pkt_wrapper *next;
+	struct gsi_xfer_elem *xfer_elem;
+	LIST_HEAD(pkt_list);
+	int ret;
+	int i;
+
+	ipa_assert(num_desc);
+	ipa_assert(num_desc <= ipa_client_tlv_count(sys->ep->client));
+
+	xfer_elem = kcalloc(num_desc, sizeof(*xfer_elem), GFP_ATOMIC);
+	if (!xfer_elem)
+		return -ENOMEM;
+
+	/* Within loop, all errors are allocation or DMA mapping */
+	ret = -ENOMEM;
+	first = NULL;
+	for (i = 0; i < num_desc; i++) {
+		dma_addr_t phys;
+
+		tx_pkt = kmem_cache_zalloc(ipa_ctx->dp->tx_pkt_wrapper_cache,
+					   GFP_ATOMIC);
+		if (!tx_pkt)
+			goto err_unwind;
+
+		if (!first)
+			first = tx_pkt;
+
+		if (desc[i].type == IPA_DATA_DESC_SKB_PAGED)
+			phys = skb_frag_dma_map(ipa_ctx->dev, desc[i].payload,
+						0, desc[i].len_opcode,
+						DMA_TO_DEVICE);
+		else
+			phys = dma_map_single(ipa_ctx->dev, desc[i].payload,
+					      desc[i].len_opcode,
+					      DMA_TO_DEVICE);
+		if (dma_mapping_error(ipa_ctx->dev, phys)) {
+			ipa_err("dma mapping error on descriptor\n");
+			kmem_cache_free(ipa_ctx->dp->tx_pkt_wrapper_cache,
+					tx_pkt);
+			goto err_unwind;
+		}
+
+		tx_pkt->type = desc[i].type;
+		tx_pkt->sys = sys;
+		tx_pkt->mem.virt = desc[i].payload;
+		tx_pkt->mem.phys = phys;
+		tx_pkt->mem.size = desc[i].len_opcode;
+		tx_pkt->callback = desc[i].callback;
+		tx_pkt->user1 = desc[i].user1;
+		tx_pkt->user2 = desc[i].user2;
+		list_add_tail(&tx_pkt->link, &pkt_list);
+
+		xfer_elem[i].addr = tx_pkt->mem.phys;
+		if (desc[i].type == IPA_IMM_CMD_DESC)
+			xfer_elem[i].type = GSI_XFER_ELEM_IMME_CMD;
+		else
+			xfer_elem[i].type = GSI_XFER_ELEM_DATA;
+		xfer_elem[i].len_opcode = desc[i].len_opcode;
+		if (i < num_desc - 1)
+			xfer_elem[i].flags = GSI_XFER_FLAG_CHAIN;
+	}
+
+	/* Fill in extra fields in the first TX packet */
+	first->cnt = num_desc;
+	INIT_WORK(&first->done_work, ipa_wq_write_done);
+
+	/* Fill in extra fields in the last transfer element */
+	if (!sys->tx.no_intr) {
+		xfer_elem[num_desc - 1].flags = GSI_XFER_FLAG_EOT;
+		xfer_elem[num_desc - 1].flags |= GSI_XFER_FLAG_BEI;
+	}
+	xfer_elem[num_desc - 1].user_data = first;
+
+	spin_lock_bh(&sys->spinlock);
+
+	list_splice_tail_init(&pkt_list, &sys->head_desc_list);
+	ret = gsi_channel_queue(ipa_ctx->gsi, sys->ep->channel_id, num_desc,
+				xfer_elem, true);
+	if (ret)
+		list_cut_end(&pkt_list, &sys->head_desc_list, &first->link);
+
+	spin_unlock_bh(&sys->spinlock);
+
+	kfree(xfer_elem);
+
+	if (!ret) {
+		if (sys->tx.no_intr)
+			ipa_nop_timer_schedule(sys);
+		return 0;
+	}
+err_unwind:
+	list_for_each_entry_safe(tx_pkt, next, &pkt_list, link) {
+		list_del(&tx_pkt->link);
+		tx_pkt->callback = NULL; /* Avoid doing the callback */
+		ipa_tx_complete(tx_pkt);
+	}
+
+	return ret;
+}
+
+/**
+ * ipa_send_cmd_timeout_complete() - Command completion callback
+ * @user1:	Opaque value carried by the command
+ * @ignored:	Second opaque value (ignored)
+ *
+ * Schedule a completion to signal that a command is done.  Free the
+ * tag_completion structure if its reference count reaches zero.
+ */
+static void ipa_send_cmd_timeout_complete(void *user1, int ignored)
+{
+	struct ipa_tag_completion *comp = user1;
+
+	complete(&comp->comp);
+	if (!atomic_dec_return(&comp->cnt))
+		kfree(comp);
+}
+
+/**
+ * ipa_send_cmd_timeout() - Send an immediate command with timeout
+ * @desc:	descriptor structure
+ * @timeout:	milliseconds to wait (or 0 to wait indefinitely)
+ *
+ * Send an immediate command, and wait for it to complete.  If
+ * timeout is non-zero it indicates the number of milliseconds to
+ * wait to receive the acknowledgment from the hardware before
+ * timing out.  If 0 is supplied, wait will not time out.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+int ipa_send_cmd_timeout(struct ipa_desc *desc, u32 timeout)
+{
+	struct ipa_tag_completion *comp;
+	unsigned long timeout_jiffies;
+	struct ipa_ep_context *ep;
+	int ret;
+
+	comp = kzalloc(sizeof(*comp), GFP_KERNEL);
+	if (!comp)
+		return -ENOMEM;
+
+	/* The reference count is decremented both here and in ack
+	 * callback.  Whichever reaches 0 frees the structure.
+	 */
+	atomic_set(&comp->cnt, 2);
+	init_completion(&comp->comp);
+
+	/* Fill in the callback info (the sole descriptor is the last) */
+	desc->callback = ipa_send_cmd_timeout_complete;
+	desc->user1 = comp;
+
+	ep = &ipa_ctx->ep[ipa_client_ep_id(IPA_CLIENT_APPS_CMD_PROD)];
+	ret = ipa_send(ep->sys, 1, desc);
+	if (ret) {
+		/* Callback won't run; drop reference on its behalf */
+		atomic_dec(&comp->cnt);
+		goto out;
+	}
+
+	timeout_jiffies = msecs_to_jiffies(timeout);
+	if (!timeout_jiffies) {
+		wait_for_completion(&comp->comp);
+	} else if (!wait_for_completion_timeout(&comp->comp, timeout_jiffies)) {
+		ret = -ETIMEDOUT;
+		ipa_err("command timed out\n");
+	}
+out:
+	if (!atomic_dec_return(&comp->cnt))
+		kfree(comp);
+
+	return ret;
+}
+
+/**
+ * ipa_handle_rx_core() - Core packet reception handling
+ * @sys:	System context for endpoint receiving packets
+ *
+ * Return:	The number of packets processed, or a negative error code
+ */
+static int ipa_handle_rx_core(struct ipa_sys_context *sys)
+{
+	int cnt;
+
+	/* Stop if the endpoint leaves polling state */
+	cnt = 0;
+	while (ipa_ep_polling(sys->ep)) {
+		int ret = ipa_poll_gsi_pkt(sys);
+
+		if (ret < 0)
+			break;
+
+		ipa_rx_common(sys, (u32)ret);
+
+		cnt++;
+	}
+
+	return cnt;
+}
+
+/**
+ * ipa_rx_switch_to_intr_mode() - Switch from polling to interrupt mode
+ * @sys:	System context for endpoint switching mode
+ */
+static void ipa_rx_switch_to_intr_mode(struct ipa_sys_context *sys)
+{
+	if (!atomic_xchg(&sys->rx.curr_polling_state, 0)) {
+		ipa_err("already in intr mode\n");
+		queue_delayed_work(sys->wq, &sys->rx.switch_to_intr_work,
+				   msecs_to_jiffies(1));
+		return;
+	}
+	ipa_dec_release_wakelock();
+	gsi_channel_intr_enable(ipa_ctx->gsi, sys->ep->channel_id);
+}
+
+void ipa_rx_switch_to_poll_mode(struct ipa_sys_context *sys)
+{
+	if (atomic_xchg(&sys->rx.curr_polling_state, 1))
+		return;
+	gsi_channel_intr_disable(ipa_ctx->gsi, sys->ep->channel_id);
+	ipa_inc_acquire_wakelock();
+	queue_work(sys->wq, &sys->rx.work);
+}
+
+/**
+ * ipa_handle_rx() - Handle packet reception.
+ * @sys:	System context for endpoint receiving packets
+ */
+static void ipa_handle_rx(struct ipa_sys_context *sys)
+{
+	int inactive_cycles = 0;
+	int cnt;
+
+	ipa_client_add();
+	do {
+		cnt = ipa_handle_rx_core(sys);
+		if (cnt == 0)
+			inactive_cycles++;
+		else
+			inactive_cycles = 0;
+
+		usleep_range(POLLING_MIN_SLEEP_RX, POLLING_MAX_SLEEP_RX);
+
+		/* if endpoint is out of buffers there is no point polling for
+		 * completed descs; release the worker so delayed work can
+		 * run in a timely manner
+		 */
+		if (sys->len - sys->rx.len_pending_xfer == 0)
+			break;
+
+	} while (inactive_cycles <= POLLING_INACTIVITY_RX);
+
+	ipa_rx_switch_to_intr_mode(sys);
+	ipa_client_remove();
+}
+
+static void ipa_switch_to_intr_rx_work_func(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct ipa_sys_context *sys;
+
+	sys = container_of(dwork, struct ipa_sys_context,
+			   rx.switch_to_intr_work);
+
+	/* For NAPI, interrupt mode is done in ipa_rx_poll context */
+	ipa_assert(!sys->ep->napi_enabled);
+
+	ipa_handle_rx(sys);
+}
+
+static struct ipa_sys_context *ipa_ep_sys_create(enum ipa_client_type client)
+{
+	const unsigned int wq_flags = WQ_MEM_RECLAIM | WQ_UNBOUND;
+	struct ipa_sys_context *sys;
+
+	/* Caller will zero all "mutable" fields; we fill in the rest */
+	sys = kmalloc(sizeof(*sys), GFP_KERNEL);
+	if (!sys)
+		return NULL;
+
+	sys->wq = alloc_workqueue("ipawq%u", wq_flags, 1, (u32)client);
+	if (!sys->wq) {
+		kfree(sys);
+		return NULL;
+	}
+
+	/* Caller assigns sys->ep = ep */
+	INIT_LIST_HEAD(&sys->head_desc_list);
+	spin_lock_init(&sys->spinlock);
+
+	return sys;
+}
+
+/**
+ * ipa_tx_dp_complete() - Transmit complete callback
+ * @user1:	Caller-supplied pointer value
+ * @user2:	Caller-supplied integer value
+ *
+ * Calls the endpoint's client_notify function if it exists;
+ * otherwise just frees the socket buffer (supplied in user1).
+ */
+static void ipa_tx_dp_complete(void *user1, int user2)
+{
+	struct sk_buff *skb = user1;
+	int ep_id = user2;
+
+	if (ipa_ctx->ep[ep_id].client_notify) {
+		unsigned long data;
+		void *priv;
+
+		priv = ipa_ctx->ep[ep_id].priv;
+		data = (unsigned long)skb;
+		ipa_ctx->ep[ep_id].client_notify(priv, IPA_WRITE_DONE, data);
+	} else {
+		dev_kfree_skb_any(skb);
+	}
+}
+
+/**
+ * ipa_tx_dp() - Transmit a socket buffer for APPS_WAN_PROD
+ * @client:	IPA client that is sending packets (WAN producer)
+ * @skb:	The socket buffer to send
+ *
+ * Returns:	0 if successful, or a negative error code
+ */
+int ipa_tx_dp(enum ipa_client_type client, struct sk_buff *skb)
+{
+	struct ipa_desc _desc = { };	/* Used for common case */
+	struct ipa_desc *desc;
+	u32 tlv_count;
+	int data_idx;
+	u32 nr_frags;
+	u32 ep_id;
+	int ret;
+	u32 f;
+
+	if (!skb->len)
+		return -EINVAL;
+
+	ep_id = ipa_client_ep_id(client);
+
+	/* Make sure source endpoint's TLV FIFO has enough entries to
+	 * hold the linear portion of the skb and all its frags.
+	 * If not, see if we can linearize it before giving up.
+	 */
+	nr_frags = skb_shinfo(skb)->nr_frags;
+	tlv_count = ipa_client_tlv_count(client);
+	if (1 + nr_frags > tlv_count) {
+		if (skb_linearize(skb))
+			return -ENOMEM;
+		nr_frags = 0;
+	}
+	if (nr_frags) {
+		desc = kcalloc(1 + nr_frags, sizeof(*desc), GFP_ATOMIC);
+		if (!desc)
+			return -ENOMEM;
+	} else {
+		desc = &_desc;	/* Default, linear case */
+	}
+
+	/* Fill in the IPA request descriptors--one for the linear
+	 * data in the skb, one each for each of its fragments.
+	 */
+	data_idx = 0;
+	desc[data_idx].payload = skb->data;
+	desc[data_idx].len_opcode = skb_headlen(skb);
+	desc[data_idx].type = IPA_DATA_DESC_SKB;
+	for (f = 0; f < nr_frags; f++) {
+		data_idx++;
+		desc[data_idx].payload = &skb_shinfo(skb)->frags[f];
+		desc[data_idx].type = IPA_DATA_DESC_SKB_PAGED;
+		desc[data_idx].len_opcode =
+				skb_frag_size(desc[data_idx].payload);
+	}
+
+	/* Have the skb be freed after the last descriptor completes. */
+	desc[data_idx].callback = ipa_tx_dp_complete;
+	desc[data_idx].user1 = skb;
+	desc[data_idx].user2 = ep_id;
+
+	ret = ipa_send(ipa_ctx->ep[ep_id].sys, data_idx + 1, desc);
+
+	if (nr_frags)
+		kfree(desc);
+
+	return ret;
+}
+
+static void ipa_wq_handle_rx(struct work_struct *work)
+{
+	struct ipa_sys_context *sys;
+
+	sys = container_of(work, struct ipa_sys_context, rx.work);
+
+	if (sys->ep->napi_enabled) {
+		ipa_client_add();
+		sys->ep->client_notify(sys->ep->priv, IPA_CLIENT_START_POLL, 0);
+	} else {
+		ipa_handle_rx(sys);
+	}
+}
+
+static int
+queue_rx_cache(struct ipa_sys_context *sys, struct ipa_rx_pkt_wrapper *rx_pkt)
+{
+	struct gsi_xfer_elem gsi_xfer_elem;
+	bool ring_doorbell;
+	int ret;
+
+	/* Don't bother zeroing this; we fill all fields */
+	gsi_xfer_elem.addr = rx_pkt->dma_addr;
+	gsi_xfer_elem.len_opcode = sys->rx.buff_sz;
+	gsi_xfer_elem.flags = GSI_XFER_FLAG_EOT;
+	gsi_xfer_elem.flags |= GSI_XFER_FLAG_EOB;
+	gsi_xfer_elem.type = GSI_XFER_ELEM_DATA;
+	gsi_xfer_elem.user_data = rx_pkt;
+
+	/* Doorbell is expensive; only ring it when a batch is queued */
+	ring_doorbell = sys->rx.len_pending_xfer++ >= IPA_REPL_XFER_THRESH;
+
+	ret = gsi_channel_queue(ipa_ctx->gsi, sys->ep->channel_id,
+				1, &gsi_xfer_elem, ring_doorbell);
+	if (ret)
+		return ret;
+
+	if (ring_doorbell)
+		sys->rx.len_pending_xfer = 0;
+
+	return 0;
+}
+
+/**
+ * ipa_replenish_rx_cache() - Replenish the Rx packets cache.
+ * @sys:	System context for IPA->AP endpoint
+ *
+ * Allocate RX packet wrapper structures with maximal socket buffers
+ * for an endpoint.  These are supplied to the hardware, which fills
+ * them with incoming data.
+ */
+static void ipa_replenish_rx_cache(struct ipa_sys_context *sys)
+{
+	struct ipa_rx_pkt_wrapper *rx_pkt;
+	struct device *dev = ipa_ctx->dev;
+	u32 rx_len_cached = sys->len;
+
+	while (rx_len_cached < sys->rx.pool_sz) {
+		gfp_t flag = GFP_NOWAIT | __GFP_NOWARN;
+		void *ptr;
+		int ret;
+
+		rx_pkt = kmem_cache_zalloc(ipa_ctx->dp->rx_pkt_wrapper_cache,
+					   flag);
+		if (!rx_pkt)
+			goto fail_kmem_cache_alloc;
+
+		INIT_LIST_HEAD(&rx_pkt->link);
+
+		rx_pkt->skb = __dev_alloc_skb(sys->rx.buff_sz, flag);
+		if (!rx_pkt->skb) {
+			ipa_err("failed to alloc skb\n");
+			goto fail_skb_alloc;
+		}
+		ptr = skb_put(rx_pkt->skb, sys->rx.buff_sz);
+		rx_pkt->dma_addr = dma_map_single(dev, ptr, sys->rx.buff_sz,
+						  DMA_FROM_DEVICE);
+		if (dma_mapping_error(dev, rx_pkt->dma_addr)) {
+			ipa_err("dma_map_single failure %p for %p\n",
+				(void *)rx_pkt->dma_addr, ptr);
+			goto fail_dma_mapping;
+		}
+
+		list_add_tail(&rx_pkt->link, &sys->head_desc_list);
+		rx_len_cached = ++sys->len;
+
+		ret = queue_rx_cache(sys, rx_pkt);
+		if (ret)
+			goto fail_provide_rx_buffer;
+	}
+
+	return;
+
+fail_provide_rx_buffer:
+	list_del(&rx_pkt->link);
+	rx_len_cached = --sys->len;
+	dma_unmap_single(dev, rx_pkt->dma_addr, sys->rx.buff_sz,
+			 DMA_FROM_DEVICE);
+fail_dma_mapping:
+	dev_kfree_skb_any(rx_pkt->skb);
+fail_skb_alloc:
+	kmem_cache_free(ipa_ctx->dp->rx_pkt_wrapper_cache, rx_pkt);
+fail_kmem_cache_alloc:
+	if (rx_len_cached - sys->rx.len_pending_xfer == 0)
+		queue_delayed_work(sys->wq, &sys->rx.replenish_work,
+				   msecs_to_jiffies(1));
+}
+
+static void ipa_replenish_rx_work_func(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct ipa_sys_context *sys;
+
+	sys = container_of(dwork, struct ipa_sys_context, rx.replenish_work);
+	ipa_client_add();
+	ipa_replenish_rx_cache(sys);
+	ipa_client_remove();
+}
+
+/** ipa_cleanup_rx() - release RX queue resources */
+static void ipa_cleanup_rx(struct ipa_sys_context *sys)
+{
+	struct ipa_rx_pkt_wrapper *rx_pkt;
+	struct ipa_rx_pkt_wrapper *r;
+
+	list_for_each_entry_safe(rx_pkt, r, &sys->head_desc_list, link) {
+		list_del(&rx_pkt->link);
+		dma_unmap_single(ipa_ctx->dev, rx_pkt->dma_addr,
+				 sys->rx.buff_sz, DMA_FROM_DEVICE);
+		dev_kfree_skb_any(rx_pkt->skb);
+		kmem_cache_free(ipa_ctx->dp->rx_pkt_wrapper_cache, rx_pkt);
+	}
+}
+
+static struct sk_buff *ipa_skb_copy_for_client(struct sk_buff *skb, int len)
+{
+	struct sk_buff *skb2;
+
+	skb2 = __dev_alloc_skb(len + IPA_RX_BUFF_CLIENT_HEADROOM, GFP_KERNEL);
+	if (likely(skb2)) {
+		/* Set the data pointer */
+		skb_reserve(skb2, IPA_RX_BUFF_CLIENT_HEADROOM);
+		memcpy(skb2->data, skb->data, len);
+		skb2->len = len;
+		skb_set_tail_pointer(skb2, len);
+	}
+
+	return skb2;
+}
+
+static struct sk_buff *ipa_join_prev_skb(struct sk_buff *prev_skb,
+					 struct sk_buff *skb, unsigned int len)
+{
+	struct sk_buff *skb2;
+
+	skb2 = skb_copy_expand(prev_skb, 0, len, GFP_KERNEL);
+	if (likely(skb2))
+		memcpy(skb_put(skb2, len), skb->data, len);
+	else
+		ipa_err("copy expand failed\n");
+	dev_kfree_skb_any(prev_skb);
+
+	return skb2;
+}
+
+static bool ipa_status_opcode_supported(enum ipahal_pkt_status_opcode opcode)
+{
+	return opcode == IPAHAL_PKT_STATUS_OPCODE_PACKET ||
+		opcode == IPAHAL_PKT_STATUS_OPCODE_DROPPED_PACKET ||
+		opcode == IPAHAL_PKT_STATUS_OPCODE_SUSPENDED_PACKET ||
+		opcode == IPAHAL_PKT_STATUS_OPCODE_PACKET_2ND_PASS;
+}
+
+static void
+ipa_lan_rx_pyld_hdlr(struct sk_buff *skb, struct ipa_sys_context *sys)
+{
+	struct ipahal_pkt_status status;
+	struct sk_buff *skb2;
+	unsigned long unused;
+	unsigned int align;
+	unsigned int used;
+	unsigned char *buf;
+	u32 pkt_status_sz;
+	int pad_len_byte;
+	u32 ep_id;
+	int len;
+	int len2;
+
+	pkt_status_sz = ipahal_pkt_status_get_size();
+	used = *(unsigned int *)skb->cb;
+	align = ALIGN(used, 32);
+	unused = IPA_RX_BUFFER_SIZE - used;
+
+	ipa_assert(skb->len);
+
+	if (sys->rx.len_partial) {
+		buf = skb_push(skb, sys->rx.len_partial);
+		memcpy(buf, sys->rx.prev_skb->data, sys->rx.len_partial);
+		sys->rx.len_partial = 0;
+		dev_kfree_skb_any(sys->rx.prev_skb);
+		sys->rx.prev_skb = NULL;
+		goto begin;
+	}
+
+	/* this endpoint has TX comp (status only) + mux-ed LAN RX data
+	 * (status+data)
+	 */
+	if (sys->rx.len_rem) {
+		if (sys->rx.len_rem <= skb->len) {
+			if (sys->rx.prev_skb) {
+				skb2 = skb_copy_expand(sys->rx.prev_skb, 0,
+						       sys->rx.len_rem,
+						       GFP_KERNEL);
+				if (likely(skb2)) {
+					memcpy(skb_put(skb2, sys->rx.len_rem),
+					       skb->data, sys->rx.len_rem);
+					skb_trim(skb2,
+						 skb2->len - sys->rx.len_pad);
+					skb2->truesize = skb2->len +
+						sizeof(struct sk_buff);
+					if (sys->rx.drop_packet)
+						dev_kfree_skb_any(skb2);
+					else
+						sys->ep->client_notify(
+							sys->ep->priv,
+							IPA_RECEIVE,
+							(unsigned long)(skb2));
+				} else {
+					ipa_err("copy expand failed\n");
+				}
+				dev_kfree_skb_any(sys->rx.prev_skb);
+			}
+			skb_pull(skb, sys->rx.len_rem);
+			sys->rx.prev_skb = NULL;
+			sys->rx.len_rem = 0;
+			sys->rx.len_pad = 0;
+		} else {
+			if (sys->rx.prev_skb) {
+				skb2 = ipa_join_prev_skb(sys->rx.prev_skb, skb,
+							 skb->len);
+				dev_kfree_skb_any(sys->rx.prev_skb);
+				sys->rx.prev_skb = skb2;
+			}
+			sys->rx.len_rem -= skb->len;
+			return;
+		}
+	}
+
+begin:
+	while (skb->len) {
+		sys->rx.drop_packet = false;
+
+		if (skb->len < pkt_status_sz) {
+			WARN_ON(sys->rx.prev_skb);
+			sys->rx.prev_skb = skb_copy(skb, GFP_KERNEL);
+			sys->rx.len_partial = skb->len;
+			return;
+		}
+
+		ipahal_pkt_status_parse(skb->data, &status);
+
+		if (!ipa_status_opcode_supported(status.status_opcode)) {
+			ipa_err("unsupported opcode(%d)\n",
+				status.status_opcode);
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+
+		if (status.pkt_len == 0) {
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+
+		if (status.endp_dest_idx == (sys->ep - ipa_ctx->ep)) {
+			/* RX data */
+			ep_id = status.endp_src_idx;
+
+			/* A packet which is received back to the AP after
+			 * there was no route match.
+			 */
+
+			if (status.exception ==
+				IPAHAL_PKT_STATUS_EXCEPTION_NONE &&
+			    status.rt_miss)
+				sys->rx.drop_packet = true;
+			if (skb->len == pkt_status_sz &&
+			    status.exception ==
+					IPAHAL_PKT_STATUS_EXCEPTION_NONE) {
+				WARN_ON(sys->rx.prev_skb);
+				sys->rx.prev_skb = skb_copy(skb, GFP_KERNEL);
+				sys->rx.len_partial = skb->len;
+				return;
+			}
+
+			pad_len_byte = ((status.pkt_len + 3) & ~3) -
+					status.pkt_len;
+
+			len = status.pkt_len + pad_len_byte +
+				IPA_SIZE_DL_CSUM_META_TRAILER;
+
+			if (status.exception ==
+					IPAHAL_PKT_STATUS_EXCEPTION_DEAGGR) {
+				sys->rx.drop_packet = true;
+			}
+
+			len2 = min(status.pkt_len + pkt_status_sz, skb->len);
+			skb2 = ipa_skb_copy_for_client(skb, len2);
+			if (likely(skb2)) {
+				if (skb->len < len + pkt_status_sz) {
+					sys->rx.prev_skb = skb2;
+					sys->rx.len_rem = len - skb->len +
+						pkt_status_sz;
+					sys->rx.len_pad = pad_len_byte;
+					skb_pull(skb, skb->len);
+				} else {
+					skb_trim(skb2, status.pkt_len +
+							pkt_status_sz);
+					if (sys->rx.drop_packet) {
+						dev_kfree_skb_any(skb2);
+					} else {
+						skb2->truesize =
+							skb2->len +
+							sizeof(struct sk_buff) +
+							(ALIGN(len +
+							pkt_status_sz, 32) *
+							unused / align);
+						sys->ep->client_notify(
+							sys->ep->priv,
+							IPA_RECEIVE,
+							(unsigned long)(skb2));
+					}
+					skb_pull(skb, len + pkt_status_sz);
+				}
+			} else {
+				ipa_err("fail to alloc skb\n");
+				if (skb->len < len) {
+					sys->rx.prev_skb = NULL;
+					sys->rx.len_rem = len - skb->len +
+						pkt_status_sz;
+					sys->rx.len_pad = pad_len_byte;
+					skb_pull(skb, skb->len);
+				} else {
+					skb_pull(skb, len + pkt_status_sz);
+				}
+			}
+		} else {
+			skb_pull(skb, pkt_status_sz);
+		}
+	}
+}
+
+static void
+ipa_wan_rx_handle_splt_pyld(struct sk_buff *skb, struct ipa_sys_context *sys)
+{
+	struct sk_buff *skb2;
+
+	if (sys->rx.len_rem <= skb->len) {
+		if (sys->rx.prev_skb) {
+			skb2 = ipa_join_prev_skb(sys->rx.prev_skb, skb,
+						 sys->rx.len_rem);
+			if (likely(skb2)) {
+				skb_pull(skb2, ipahal_pkt_status_get_size());
+				skb2->truesize = skb2->len +
+					sizeof(struct sk_buff);
+				sys->ep->client_notify(sys->ep->priv,
+						       IPA_RECEIVE,
+						       (unsigned long)skb2);
+			}
+		}
+		skb_pull(skb, sys->rx.len_rem);
+		sys->rx.prev_skb = NULL;
+		sys->rx.len_rem = 0;
+	} else {
+		if (sys->rx.prev_skb) {
+			skb2 = ipa_join_prev_skb(sys->rx.prev_skb, skb,
+						 skb->len);
+			sys->rx.prev_skb = skb2;
+		}
+		sys->rx.len_rem -= skb->len;
+		skb_pull(skb, skb->len);
+	}
+}
+
+static void
+ipa_wan_rx_pyld_hdlr(struct sk_buff *skb, struct ipa_sys_context *sys)
+{
+	struct ipahal_pkt_status status;
+	unsigned char *skb_data;
+	struct sk_buff *skb2;
+	u16 pkt_len_with_pad;
+	unsigned long unused;
+	unsigned int align;
+	unsigned int used;
+	u32 pkt_status_sz;
+	int frame_len;
+	u32 qmap_hdr;
+	int checksum;
+	int ep_id;
+
+	used = *(unsigned int *)skb->cb;
+	align = ALIGN(used, 32);
+	unused = IPA_RX_BUFFER_SIZE - used;
+
+	ipa_assert(skb->len);
+
+	if (ipa_ctx->ipa_client_apps_wan_cons_agg_gro) {
+		sys->ep->client_notify(sys->ep->priv, IPA_RECEIVE,
+				       (unsigned long)(skb));
+		return;
+	}
+
+	/* payload splits across 2 buff or more,
+	 * take the start of the payload from rx.prev_skb
+	 */
+	if (sys->rx.len_rem)
+		ipa_wan_rx_handle_splt_pyld(skb, sys);
+
+	pkt_status_sz = ipahal_pkt_status_get_size();
+	while (skb->len) {
+		u32 status_mask;
+
+		if (skb->len < pkt_status_sz) {
+			ipa_err("status straddles buffer\n");
+			WARN_ON(1);
+			goto bail;
+		}
+		ipahal_pkt_status_parse(skb->data, &status);
+		skb_data = skb->data;
+
+		if (!ipa_status_opcode_supported(status.status_opcode) ||
+		    status.status_opcode ==
+				IPAHAL_PKT_STATUS_OPCODE_SUSPENDED_PACKET) {
+			ipa_err("unsupported opcode(%d)\n",
+				status.status_opcode);
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+
+		if (status.endp_dest_idx >= ipa_ctx->ep_count ||
+		    status.endp_src_idx >= ipa_ctx->ep_count ||
+		    status.pkt_len > IPA_GENERIC_AGGR_BYTE_LIMIT) {
+			ipa_err("status fields invalid\n");
+			WARN_ON(1);
+			goto bail;
+		}
+		if (status.pkt_len == 0) {
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+		ep_id = ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS);
+		if (status.endp_dest_idx != ep_id) {
+			ipa_err("expected endp_dest_idx %d received %d\n",
+				ep_id, status.endp_dest_idx);
+			WARN_ON(1);
+			goto bail;
+		}
+		/* RX data */
+		if (skb->len == pkt_status_sz) {
+			ipa_err("Ins header in next buffer\n");
+			WARN_ON(1);
+			goto bail;
+		}
+		qmap_hdr = *(u32 *)(skb_data + pkt_status_sz);
+
+		/* Take the pkt_len_with_pad from the last 2 bytes of the QMAP
+		 * header
+		 */
+		/*QMAP is BE: convert the pkt_len field from BE to LE*/
+		pkt_len_with_pad = ntohs((qmap_hdr >> 16) & 0xffff);
+		/*get the CHECKSUM_PROCESS bit*/
+		status_mask = status.status_mask;
+		checksum = status_mask & IPAHAL_PKT_STATUS_MASK_CKSUM_PROCESS;
+
+		frame_len = pkt_status_sz + IPA_QMAP_HEADER_LENGTH +
+			    pkt_len_with_pad;
+		if (checksum)
+			frame_len += IPA_DL_CHECKSUM_LENGTH;
+
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (likely(skb2)) {
+			/* the len of actual data is smaller than expected
+			 * payload split across 2 buff
+			 */
+			if (skb->len < frame_len) {
+				sys->rx.prev_skb = skb2;
+				sys->rx.len_rem = frame_len - skb->len;
+				skb_pull(skb, skb->len);
+			} else {
+				skb_trim(skb2, frame_len);
+				skb_pull(skb2, pkt_status_sz);
+				skb2->truesize = skb2->len +
+					sizeof(struct sk_buff) +
+					(ALIGN(frame_len, 32) *
+					 unused / align);
+				sys->ep->client_notify(sys->ep->priv,
+						       IPA_RECEIVE,
+						       (unsigned long)(skb2));
+				skb_pull(skb, frame_len);
+			}
+		} else {
+			ipa_err("fail to clone\n");
+			if (skb->len < frame_len) {
+				sys->rx.prev_skb = NULL;
+				sys->rx.len_rem = frame_len - skb->len;
+				skb_pull(skb, skb->len);
+			} else {
+				skb_pull(skb, frame_len);
+			}
+		}
+	}
+bail:
+	dev_kfree_skb_any(skb);
+}
+
+void ipa_lan_rx_cb(void *priv, enum ipa_dp_evt_type evt, unsigned long data)
+{
+	struct sk_buff *rx_skb = (struct sk_buff *)data;
+	struct ipahal_pkt_status status;
+	struct ipa_ep_context *ep;
+	u32 pkt_status_size;
+	u32 metadata;
+	u32 ep_id;
+
+	pkt_status_size = ipahal_pkt_status_get_size();
+
+	ipa_assert(rx_skb->len >= pkt_status_size);
+
+	ipahal_pkt_status_parse(rx_skb->data, &status);
+	ep_id = status.endp_src_idx;
+	metadata = status.metadata;
+	ep = &ipa_ctx->ep[ep_id];
+	if (ep_id >= ipa_ctx->ep_count || !ep->allocated ||
+	    !ep->client_notify) {
+		ipa_err("drop endpoint=%u allocated=%s client_notify=%p\n",
+			ep_id, ep->allocated ? "true" : "false",
+			ep->client_notify);
+		dev_kfree_skb_any(rx_skb);
+		return;
+	}
+
+	/* Consume the status packet, and if no exception, the header */
+	skb_pull(rx_skb, pkt_status_size);
+	if (status.exception == IPAHAL_PKT_STATUS_EXCEPTION_NONE)
+		skb_pull(rx_skb, IPA_LAN_RX_HEADER_LENGTH);
+
+	/* Metadata Info
+	 *  ------------------------------------------
+	 *  |	3     |	  2	|    1	      |	 0   |
+	 *  | fw_desc | vdev_id | qmap mux id | Resv |
+	 *  ------------------------------------------
+	 */
+	*(u16 *)rx_skb->cb = ((metadata >> 16) & 0xffff);
+
+	ep->client_notify(ep->priv, IPA_RECEIVE, (unsigned long)rx_skb);
+}
+
+static void ipa_rx_common(struct ipa_sys_context *sys, u32 size)
+{
+	struct ipa_rx_pkt_wrapper *rx_pkt;
+	struct sk_buff *rx_skb;
+
+	ipa_assert(!list_empty(&sys->head_desc_list));
+
+	spin_lock_bh(&sys->spinlock);
+
+	rx_pkt = list_first_entry(&sys->head_desc_list,
+				  struct ipa_rx_pkt_wrapper, link);
+	list_del(&rx_pkt->link);
+	sys->len--;
+
+	spin_unlock_bh(&sys->spinlock);
+
+	rx_skb = rx_pkt->skb;
+	dma_unmap_single(ipa_ctx->dev, rx_pkt->dma_addr, sys->rx.buff_sz,
+			 DMA_FROM_DEVICE);
+
+	skb_trim(rx_skb, size);
+
+	*(unsigned int *)rx_skb->cb = rx_skb->len;
+	rx_skb->truesize = size + sizeof(struct sk_buff);
+
+	sys->rx.pyld_hdlr(rx_skb, sys);
+	kmem_cache_free(ipa_ctx->dp->rx_pkt_wrapper_cache, rx_pkt);
+	ipa_replenish_rx_cache(sys);
+}
+
+/**
+ * ipa_aggr_byte_limit_buf_size()
+ * @byte_limit:	Desired limit (in bytes) for aggregation
+ *
+ * Compute the buffer size required to support a requested aggregation
+ * byte limit.  Aggregration will close when *more* than the configured
+ * number of bytes have been added to an aggregation frame.  Our
+ * buffers therefore need to to be big enough to receive one complete
+ * packet once the configured byte limit has been consumed.
+ *
+ * An incoming packet can have as much as IPA_MTU of data in it, but
+ * the buffer also needs to be large enough to accomodate the standard
+ * socket buffer overhead (NET_SKB_PAD of headroom, plus an implied
+ * skb_shared_info structure at the end).
+ *
+ * So we compute the required buffer size by adding the standard
+ * socket buffer overhead and MTU to the requested size.  We round
+ * that down to a power of 2 in an effort to avoid fragmentation due
+ * to unaligned buffer sizes.
+ *
+ * After accounting for all of this, we return the number of bytes
+ * of buffer space the IPA hardware will know is available to hold
+ * received data (without any overhead).
+ *
+ * Return:	The computes size of buffer space available
+ */
+u32 ipa_aggr_byte_limit_buf_size(u32 byte_limit)
+{
+	/* Account for one additional packet, including overhead */
+	byte_limit += IPA_RX_BUFFER_RESERVED;
+	byte_limit += IPA_MTU;
+
+	/* Convert this size to a nearby power-of-2.  We choose one
+	 * that's *less than* the limit we seek--so we start by
+	 * subracting 1.  The highest set bit in that is used to
+	 * compute the power of 2.
+	 *
+	 * XXX Why is this *less than* and not possibly equal?
+	 */
+	byte_limit = 1 << __fls(byte_limit - 1);
+
+	/* Given that size, figure out how much buffer space that
+	 * leaves us for received data.
+	 */
+	return IPA_RX_BUFFER_AVAILABLE(byte_limit);
+}
+
+void ipa_gsi_irq_tx_notify_cb(void *xfer_data)
+{
+	struct ipa_tx_pkt_wrapper *tx_pkt = xfer_data;
+
+	queue_work(tx_pkt->sys->wq, &tx_pkt->done_work);
+}
+
+void ipa_gsi_irq_rx_notify_cb(void *chan_data, u16 count)
+{
+	struct ipa_sys_context *sys = chan_data;
+
+	sys->ep->bytes_xfered_valid = true;
+	sys->ep->bytes_xfered = count;
+
+	ipa_rx_switch_to_poll_mode(sys);
+}
+
+static int ipa_gsi_setup_channel(struct ipa_ep_context *ep, u32 channel_count,
+				 u32 evt_ring_mult)
+{
+	u32 channel_id = ipa_client_channel_id(ep->client);
+	u32 tlv_count = ipa_client_tlv_count(ep->client);
+	bool from_ipa = ipa_consumer(ep->client);
+	bool moderation;
+	bool priority;
+	int ret;
+
+	priority = ep->client == IPA_CLIENT_APPS_CMD_PROD;
+	moderation = !ep->sys->tx.no_intr;
+
+	ret = gsi_channel_alloc(ipa_ctx->gsi, channel_id, channel_count,
+				from_ipa, priority, evt_ring_mult, moderation,
+				ep->sys);
+	if (ret)
+		return ret;
+	ep->channel_id = channel_id;
+
+	gsi_channel_scratch_write(ipa_ctx->gsi, ep->channel_id, tlv_count);
+
+	ret = gsi_channel_start(ipa_ctx->gsi, ep->channel_id);
+	if (ret)
+		gsi_channel_free(ipa_ctx->gsi, ep->channel_id);
+
+	return ret;
+}
+
+void ipa_endp_init_hdr_cons(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_cons(&ep->init_hdr, header_size, metadata_offset,
+				   length_offset);
+}
+
+void ipa_endp_init_hdr_prod(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_prod(&ep->init_hdr, header_size, metadata_offset,
+				   length_offset);
+}
+
+void
+ipa_endp_init_hdr_ext_cons(u32 ep_id, u32 pad_align, bool pad_included)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_ext_cons(&ep->hdr_ext, pad_align, pad_included);
+}
+
+void ipa_endp_init_hdr_ext_prod(u32 ep_id, u32 pad_align)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_ext_prod(&ep->hdr_ext, pad_align);
+}
+
+void
+ipa_endp_init_aggr_cons(u32 ep_id, u32 size, u32 count, bool close_on_eof)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_aggr_cons(&ep->init_aggr, size, count, close_on_eof);
+}
+
+void ipa_endp_init_aggr_prod(u32 ep_id, enum ipa_aggr_en aggr_en,
+			     enum ipa_aggr_type aggr_type)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_aggr_prod(&ep->init_aggr, aggr_en, aggr_type);
+}
+
+void ipa_endp_init_cfg_cons(u32 ep_id, enum ipa_cs_offload_en offload_type)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_cfg_cons(&ep->init_cfg, offload_type);
+}
+
+void ipa_endp_init_cfg_prod(u32 ep_id, enum ipa_cs_offload_en offload_type,
+			    u32 metadata_offset)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_cfg_prod(&ep->init_cfg, offload_type,
+				   metadata_offset);
+}
+
+void ipa_endp_init_hdr_metadata_mask_cons(u32 ep_id, u32 mask)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_metadata_mask_cons(&ep->metadata_mask, mask);
+}
+
+void ipa_endp_init_hdr_metadata_mask_prod(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_metadata_mask_prod(&ep->metadata_mask);
+}
+
+void ipa_endp_status_cons(u32 ep_id, bool enable)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_status_cons(&ep->status, enable);
+}
+
+void ipa_endp_status_prod(u32 ep_id, bool enable,
+			  enum ipa_client_type status_client)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 status_ep_id;
+
+	status_ep_id = ipa_client_ep_id(status_client);
+
+	ipa_reg_endp_status_prod(&ep->status, enable, status_ep_id);
+}
+
+
+/* Note that the mode setting is not valid for consumer endpoints */
+void ipa_endp_init_mode_cons(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_mode_cons(&ep->init_mode);
+}
+
+void ipa_endp_init_mode_prod(u32 ep_id, enum ipa_mode mode,
+			     enum ipa_client_type dst_client)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 dst_ep_id;
+
+	dst_ep_id = ipa_client_ep_id(dst_client);
+
+	ipa_reg_endp_init_mode_prod(&ep->init_mode, mode, dst_ep_id);
+}
+
+/* XXX The sequencer setting seems not to be valid for consumer endpoints */
+void ipa_endp_init_seq_cons(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_seq_cons(&ep->init_seq);
+}
+
+void ipa_endp_init_seq_prod(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 seq_type;
+
+	seq_type = (u32)ipa_endp_seq_type(ep_id);
+
+	ipa_reg_endp_init_seq_prod(&ep->init_seq, seq_type);
+}
+
+/* XXX The deaggr setting seems not to be valid for consumer endpoints */
+void ipa_endp_init_deaggr_cons(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_deaggr_cons(&ep->init_deaggr);
+}
+
+void ipa_endp_init_deaggr_prod(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_deaggr_prod(&ep->init_deaggr);
+}
+
+int ipa_ep_alloc(enum ipa_client_type client)
+{
+	u32 ep_id = ipa_client_ep_id(client);
+	struct ipa_sys_context *sys;
+	struct ipa_ep_context *ep;
+
+	ep = &ipa_ctx->ep[ep_id];
+
+	ipa_assert(!ep->allocated);
+
+	/* Reuse the endpoint's sys pointer if it is initialized */
+	sys = ep->sys;
+	if (!sys) {
+		sys = ipa_ep_sys_create(client);
+		if (!sys)
+			return -ENOMEM;
+		sys->ep = ep;
+	}
+
+	/* Zero the "mutable" part of the system context */
+	memset(sys, 0, offsetof(struct ipa_sys_context, ep));
+
+	/* Initialize the endpoint context */
+	memset(ep, 0, sizeof(*ep));
+	ep->sys = sys;
+	ep->client = client;
+	ep->allocated = true;
+
+	return ep_id;
+}
+
+void ipa_ep_free(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_assert(ep->allocated);
+
+	ep->allocated = false;
+}
+
+/**
+ * ipa_ep_setup() - Set up an IPA endpoint
+ * @ep_id:		Endpoint to set up
+ * @channel_count:	Number of transfer elements in the channel
+ * @evt_ring_mult:	Used to determine number of elements in event ring
+ * @rx_buffer_size:	Receive buffer size to use (or 0 for TX endpoitns)
+ * @client_notify:	Notify function to call on completion
+ * @priv:		Value supplied to the notify function
+ *
+ * Returns:	0 if successful, or a negative error code
+ */
+int ipa_ep_setup(u32 ep_id, u32 channel_count, u32 evt_ring_mult,
+		 u32 rx_buffer_size,
+		 void (*client_notify)(void *priv, enum ipa_dp_evt_type type,
+				       unsigned long data),
+		 void *priv)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	int ret;
+
+	if (ipa_consumer(ep->client)) {
+		atomic_set(&ep->sys->rx.curr_polling_state, 0);
+		INIT_DELAYED_WORK(&ep->sys->rx.switch_to_intr_work,
+				  ipa_switch_to_intr_rx_work_func);
+		if (ep->client == IPA_CLIENT_APPS_LAN_CONS)
+			ep->sys->rx.pyld_hdlr = ipa_lan_rx_pyld_hdlr;
+		else
+			ep->sys->rx.pyld_hdlr = ipa_wan_rx_pyld_hdlr;
+		ep->sys->rx.buff_sz = rx_buffer_size;
+		ep->sys->rx.pool_sz = IPA_GENERIC_RX_POOL_SZ;
+		INIT_WORK(&ep->sys->rx.work, ipa_wq_handle_rx);
+		INIT_DELAYED_WORK(&ep->sys->rx.replenish_work,
+				  ipa_replenish_rx_work_func);
+	}
+
+	ep->client_notify = client_notify;
+	ep->priv = priv;
+	ep->napi_enabled = ep->client == IPA_CLIENT_APPS_WAN_CONS;
+
+	ipa_client_add();
+
+	ipa_cfg_ep(ep_id);
+
+	ret = ipa_gsi_setup_channel(ep, channel_count, evt_ring_mult);
+	if (ret)
+		goto err_client_remove;
+
+	if (ipa_consumer(ep->client))
+		ipa_replenish_rx_cache(ep->sys);
+err_client_remove:
+	ipa_client_remove();
+
+	return ret;
+}
+
+/**
+ * ipa_channel_reset_aggr() - Reset with aggregation active
+ * @ep_id:	Endpoint on which reset is performed
+ *
+ * If aggregation is active on a channel when a reset is performed,
+ * a special sequence of actions must be taken.  This is a workaround
+ * for a hardware limitation.
+ *
+ * Return:	0 if successful, or a negative error code.
+ */
+static int ipa_channel_reset_aggr(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	struct ipa_reg_aggr_force_close force_close;
+	struct ipa_reg_endp_init_ctrl init_ctrl;
+	struct gsi_xfer_elem xfer_elem = { };
+	struct ipa_dma_mem dma_byte;
+	int aggr_active_bitmap = 0;
+	bool ep_suspended = false;
+	int ret;
+	int i;
+
+	ipa_reg_aggr_force_close(&force_close, BIT(ep_id));
+	ipa_write_reg_fields(IPA_AGGR_FORCE_CLOSE, &force_close);
+
+	/* Reset channel */
+	ret = gsi_channel_reset(ipa_ctx->gsi, ep->channel_id);
+	if (ret)
+		return ret;
+
+	/* Turn off the doorbell engine.  We're going to poll until
+	 * we know aggregation isn't active.
+	 */
+	gsi_channel_config(ipa_ctx->gsi, ep->channel_id, false);
+
+	ipa_read_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+	if (init_ctrl.endp_suspend) {
+		ep_suspended = true;
+		ipa_reg_endp_init_ctrl(&init_ctrl, false);
+		ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+	}
+
+	/* Start channel and put 1 Byte descriptor on it */
+	ret = gsi_channel_start(ipa_ctx->gsi, ep->channel_id);
+	if (ret)
+		goto out_suspend_again;
+
+	if (ipa_dma_alloc(&dma_byte, 1, GFP_KERNEL)) {
+		ret = -ENOMEM;
+		goto err_stop_channel;
+	}
+
+	xfer_elem.addr = dma_byte.phys;
+	xfer_elem.len_opcode = 1;	/* = dma_byte.size; */
+	xfer_elem.flags = GSI_XFER_FLAG_EOT;
+	xfer_elem.type = GSI_XFER_ELEM_DATA;
+
+	ret = gsi_channel_queue(ipa_ctx->gsi, ep->channel_id, 1, &xfer_elem,
+				true);
+	if (ret)
+		goto err_dma_free;
+
+	/* Wait for aggregation frame to be closed */
+	for (i = 0; i < CHANNEL_RESET_AGGR_RETRY_COUNT; i++) {
+		aggr_active_bitmap = ipa_read_reg(IPA_STATE_AGGR_ACTIVE);
+		if (!(aggr_active_bitmap & BIT(ep_id)))
+			break;
+		msleep(CHANNEL_RESET_DELAY);
+	}
+	ipa_bug_on(aggr_active_bitmap & BIT(ep_id));
+
+	ipa_dma_free(&dma_byte);
+
+	ret = ipa_stop_gsi_channel(ep_id);
+	if (ret)
+		goto out_suspend_again;
+
+	/* Reset the channel.  If successful we need to sleep for 1
+	 * msec to complete the GSI channel reset sequence.  Either
+	 * way we finish by suspending the channel again (if necessary)
+	 * and re-enabling its doorbell engine.
+	 */
+	ret = gsi_channel_reset(ipa_ctx->gsi, ep->channel_id);
+	if (!ret)
+		msleep(CHANNEL_RESET_DELAY);
+	goto out_suspend_again;
+
+err_dma_free:
+	ipa_dma_free(&dma_byte);
+err_stop_channel:
+	ipa_stop_gsi_channel(ep_id);
+out_suspend_again:
+	if (ep_suspended) {
+		ipa_reg_endp_init_ctrl(&init_ctrl, true);
+		ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+	}
+	/* Turn on the doorbell engine again */
+	gsi_channel_config(ipa_ctx->gsi, ep->channel_id, true);
+
+	return ret;
+}
+
+static void ipa_reset_gsi_channel(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 aggr_active_bitmap = 0;
+
+	/* For consumer endpoints, a hardware limitation prevents us
+	 * from issuing a channel reset if aggregation is active.
+	 * Check for this case, and if detected, perform a special
+	 * reset sequence.  Otherwise just do a "normal" reset.
+	 */
+	if (ipa_consumer(ep->client))
+		aggr_active_bitmap = ipa_read_reg(IPA_STATE_AGGR_ACTIVE);
+
+	if (aggr_active_bitmap & BIT(ep_id)) {
+		ipa_bug_on(ipa_channel_reset_aggr(ep_id));
+	} else {
+		/* In case the reset follows stop, need to wait 1 msec */
+		msleep(CHANNEL_RESET_DELAY);
+		ipa_bug_on(gsi_channel_reset(ipa_ctx->gsi, ep->channel_id));
+	}
+}
+
+/**
+ * ipa_ep_teardown() - Tear down an endpoint
+ * @ep_id:	The endpoint to tear down
+ */
+void ipa_ep_teardown(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	int empty;
+	int ret;
+	int i;
+
+	if (ep->napi_enabled) {
+		do {
+			usleep_range(95, 105);
+		} while (ipa_ep_polling(ep));
+	}
+
+	if (ipa_producer(ep->client)) {
+		do {
+			spin_lock_bh(&ep->sys->spinlock);
+			empty = list_empty(&ep->sys->head_desc_list);
+			spin_unlock_bh(&ep->sys->spinlock);
+			if (!empty)
+				usleep_range(95, 105);
+			else
+				break;
+		} while (1);
+	}
+
+	if (ipa_consumer(ep->client))
+		cancel_delayed_work_sync(&ep->sys->rx.replenish_work);
+	flush_workqueue(ep->sys->wq);
+	/* channel stop might fail on timeout if IPA is busy */
+	for (i = 0; i < IPA_GSI_CHANNEL_STOP_MAX_RETRY; i++) {
+		ret = ipa_stop_gsi_channel(ep_id);
+		if (!ret)
+			break;
+		ipa_bug_on(ret != -EAGAIN && ret != -ETIMEDOUT);
+	}
+
+	ipa_reset_gsi_channel(ep_id);
+	gsi_channel_free(ipa_ctx->gsi, ep->channel_id);
+
+	if (ipa_consumer(ep->client))
+		ipa_cleanup_rx(ep->sys);
+
+	ipa_ep_free(ep_id);
+}
+
+static int ipa_poll_gsi_pkt(struct ipa_sys_context *sys)
+{
+	if (sys->ep->bytes_xfered_valid) {
+		sys->ep->bytes_xfered_valid = false;
+
+		return (int)sys->ep->bytes_xfered;
+	}
+
+	return gsi_channel_poll(ipa_ctx->gsi, sys->ep->channel_id);
+}
+
+bool ipa_ep_polling(struct ipa_ep_context *ep)
+{
+	ipa_assert(ipa_consumer(ep->client));
+
+	return !!atomic_read(&ep->sys->rx.curr_polling_state);
+}
+
+struct ipa_dp *ipa_dp_init(void)
+{
+	struct kmem_cache *cache;
+	struct ipa_dp *dp;
+
+	dp = kzalloc(sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return NULL;
+
+	cache = kmem_cache_create("IPA_TX_PKT_WRAPPER",
+				  sizeof(struct ipa_tx_pkt_wrapper),
+				  0, 0, NULL);
+	if (!cache) {
+		kfree(dp);
+		return NULL;
+	}
+	dp->tx_pkt_wrapper_cache = cache;
+
+	cache = kmem_cache_create("IPA_RX_PKT_WRAPPER",
+				  sizeof(struct ipa_rx_pkt_wrapper),
+				  0, 0, NULL);
+	if (!cache) {
+		kmem_cache_destroy(dp->tx_pkt_wrapper_cache);
+		kfree(dp);
+		return NULL;
+	}
+	dp->rx_pkt_wrapper_cache = cache;
+
+	return dp;
+}
+
+void ipa_dp_exit(struct ipa_dp *dp)
+{
+	kmem_cache_destroy(dp->rx_pkt_wrapper_cache);
+	kmem_cache_destroy(dp->tx_pkt_wrapper_cache);
+	kfree(dp);
+}
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 10/12] soc: qcom: ipa: data path
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patch contains "ipa_dp.c", which includes the bulk of the data
path code.  There is an overview in the code of how things operate,
but there are already plans to rework this portion of the driver.

In particular:
  - Interrupt handling will be replaced with a threaded interrupt
    handler.  Currently handling occurs in a combination of
    interrupt and workqueue context, and this requires locking
    and atomic operations for proper synchronization.
  - Currently, only receive endpoints use NAPI.  Transmit
    completion interrupts are disabled, and are handled in batches
    by periodically scheduling an interrupting no-op request.
    The plan is to arrange for transmit requests to generate
    interrupts, and their completion will be processed with other
    completions in the NAPI poll function.  This will also allow
    accurate feedback about packet sojourn time to be provided to
    queue limiting mechanisms.
  - Not all receive endpoints use NAPI.  The plan is for *all*
    endpoints to use NAPI.  And because all endpoints share a
    common GSI interrupt, a single NAPI structure will used to
    managing the processing for all completions on all endpoints.
  - Receive buffers are posted to the hardware by a workqueue
    function.  Instead, the plan is to have this done by the
    NAPI poll routine.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/ipa_dp.c | 1994 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 1994 insertions(+)
 create mode 100644 drivers/net/ipa/ipa_dp.c

diff --git a/drivers/net/ipa/ipa_dp.c b/drivers/net/ipa/ipa_dp.c
new file mode 100644
index 000000000000..c16ac74765b8
--- /dev/null
+++ b/drivers/net/ipa/ipa_dp.c
@@ -0,0 +1,1994 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+
+#include "ipa_i.h"	/* ipa_err() */
+#include "ipahal.h"
+#include "ipa_dma.h"
+
+/**
+ * DOC:  The IPA Data Path
+ *
+ * The IPA is used to transmit data between execution environments.
+ * The data path code uses functions and structures supplied by the
+ * GSI to interact with the IPA hardware.  A packet to be transmitted
+ * or received is held in a socket buffer.  Each has a "wrapper"
+ * structure associated with it.  A GSI transfer request refers to
+ * the packet wrapper, and when queued to the hardware the packet
+ * wrapper is added to a list of outstanding requests for an endpoint
+ * (maintained in the head_desc_list in the endpoint's system context).
+ * When the GSI transfer completes, a callback function is provided
+ * the packet wrapper pointer, allowing it to be released after the
+ * received socket buffer has been passed up the stack, or a buffer
+ * whose data has been transmitted has been freed.
+ *
+ * Producer (PROD) endpoints are used to send data from the AP toward
+ * the IPA.  The common function for sending data on producer endpoints
+ * is ipa_send().  It takes a system context and an array of IPA
+ * descriptors as arguments.  Each descriptor is given a TX packet
+ * wrapper, and its content is translated into an equivalent GSI
+ * transfer element  structure after its memory address is mapped for
+ * DMA.  The GSI transfer element array is finally passed to the GSI
+ * layer using gsi_channel_queue().
+ *
+ * The code provides a "no_intr" feature, allowing endpoints to have
+ * their transmit completions not produce an interrupt.  (This
+ * behavior is used only for the modem producer.)  In this case, a
+ * no-op request is generated every 200 milliseconds while transmit
+ * requests are outstanding.  The no-op will generate an interrupt
+ * when it's complete, and its completion implies the completion of
+ * all transmit requests issued before it.  The GSI will call
+ * ipa_gsi_irq_tx_notify_cb() in response to interrupts on a producer
+ * endpoint.
+ *
+ * Receive buffers are passed to consumer (CONS) channels to be
+ * available to hold incoming data.  Arriving data is placed
+ * in these buffers, leading to events being generated on the event
+ * ring assciated with a channel.  When an interrupt occurs on a
+ * consumer endpoint, the GSI layer calls ipa_gsi_irq_rx_notify_cb().
+ * This causes the endpoint to switch to polling mode.  The
+ * completion of a receive also leads to ipa_replenish_rx_cache()
+ * being called, to replace the consumed buffer.
+ *
+ * Consumer enpoints optionally use NAPI (only the modem consumer,
+ * WWAN_CONS, does currently).  An atomic variable records whether
+ * the endpoint is in polling mode or not.  This is needed because
+ * switching to polling mode is currently done in a workqueue.  Once
+ * NAPI polling completes, and endpoint switches back to interrupt
+ * mode.
+ */
+
+/**
+ * struct ipa_tx_pkt_wrapper - IPA transmit packet wrapper
+ * @type:	type of descriptor
+ * @sys:	Corresponding IPA sys context
+ * @mem:	Memory buffer used by this packet
+ * @callback:	IPA client provided callback
+ * @user1:	Cookie1 for above callback
+ * @user2:	Cookie2 for above callback
+ * @link:	Links for the endpoint's sys->head_desc_list
+ * @cnt:	Number of descriptors in request
+ * @done_work:	Work structure used when complete
+ */
+struct ipa_tx_pkt_wrapper {
+	enum ipa_desc_type type;
+	struct ipa_sys_context *sys;
+	struct ipa_dma_mem mem;
+	void (*callback)(void *user1, int user2);
+	void *user1;
+	int user2;
+	struct list_head link;
+	u32 cnt;
+	struct work_struct done_work;
+};
+
+/** struct ipa_rx_pkt_wrapper - IPA Rx packet wrapper
+ * @link:	Links for the endpoint's sys->head_desc_list
+ * @skb:	Socket buffer containing the received packet
+ * @len:	How many bytes are copied into skb's buffer
+ */
+struct ipa_rx_pkt_wrapper {
+	struct list_head link;
+	struct sk_buff *skb;
+	dma_addr_t dma_addr;
+};
+
+/** struct ipa_sys_context - IPA GPI endpoint context
+ * @len:	The number of entries in @head_desc_list
+ * @tx:		Details related to AP->IPA endpoints
+ * @rx:		Details related to IPA->AP endpoints
+ * @ep:		Associated endpoint
+ * @head_desc_list: List of packets
+ * @spinlock:	Lock protecting the descriptor list
+ * @workqueue:	Workqueue used for this endpoint
+ */
+struct ipa_sys_context {
+	u32 len;
+	union {
+		struct {	/* Consumer endpoints only */
+			u32 len_pending_xfer;
+			atomic_t curr_polling_state;
+			struct delayed_work switch_to_intr_work; /* sys->wq */
+			void (*pyld_hdlr)(struct sk_buff *,
+					  struct ipa_sys_context *);
+			u32 buff_sz;
+			u32 pool_sz;
+			struct sk_buff *prev_skb;
+			unsigned int len_rem;
+			unsigned int len_pad;		/* APPS_LAN only */
+			unsigned int len_partial;	/* APPS_LAN only */
+			bool drop_packet;		/* APPS_LAN only */
+
+			struct work_struct work; /* sys->wq */
+			struct delayed_work replenish_work; /* sys->wq */
+		} rx;
+		struct {	/* Producer endpoints only */
+			/* no_intr/nop is APPS_WAN_PROD only */
+			bool no_intr;
+			atomic_t nop_pending;
+			struct hrtimer nop_timer;
+			struct work_struct nop_work; /* sys->wq */
+		} tx;
+	};
+
+	/* ordering is important - mutable fields go above */
+	struct ipa_ep_context *ep;
+	struct list_head head_desc_list; /* contains len entries */
+	spinlock_t spinlock;		/* protects head_desc list */
+	struct workqueue_struct *wq;
+	/* ordering is important - other immutable fields go below */
+};
+
+/**
+ * struct ipa_dp - IPA data path information
+ * @tx_pkt_wrapper_cache:	Tx packets cache
+ * @rx_pkt_wrapper_cache:	Rx packets cache
+ */
+struct ipa_dp {
+	struct kmem_cache *tx_pkt_wrapper_cache;
+	struct kmem_cache *rx_pkt_wrapper_cache;
+};
+
+/**
+ * struct ipa_tag_completion - Reference counted completion object
+ * @comp:	Completion when last reference is dropped
+ * @cnt:	Reference count
+ */
+struct ipa_tag_completion {
+	struct completion comp;
+	atomic_t cnt;
+};
+
+#define CHANNEL_RESET_AGGR_RETRY_COUNT	3
+#define CHANNEL_RESET_DELAY		1	/* milliseconds */
+
+#define IPA_QMAP_HEADER_LENGTH		4
+
+#define IPA_WAN_AGGR_PKT_CNT		5
+#define POLLING_INACTIVITY_RX		40
+#define POLLING_MIN_SLEEP_RX		1010	/* microseconds */
+#define POLLING_MAX_SLEEP_RX		1050	/* microseconds */
+
+#define IPA_RX_BUFFER_ORDER	1	/* Default RX buffer is 2^1 pages */
+#define IPA_RX_BUFFER_SIZE	(1 << (IPA_RX_BUFFER_ORDER + PAGE_SHIFT))
+
+/* The amount of RX buffer space consumed by standard skb overhead */
+#define IPA_RX_BUFFER_RESERVED \
+	(IPA_RX_BUFFER_SIZE - SKB_MAX_ORDER(NET_SKB_PAD, IPA_RX_BUFFER_ORDER))
+
+/* RX buffer space remaining after standard overhead is consumed */
+#define IPA_RX_BUFFER_AVAILABLE(X)	((X) - IPA_RX_BUFFER_RESERVED)
+
+#define IPA_RX_BUFF_CLIENT_HEADROOM	256
+
+#define IPA_SIZE_DL_CSUM_META_TRAILER	8
+
+#define IPA_REPL_XFER_THRESH		10
+
+/* How long before sending an interrupting no-op to handle TX completions */
+#define IPA_TX_NOP_DELAY_NS		(2 * 1000 * 1000)	/* 2 msec */
+
+static void ipa_rx_switch_to_intr_mode(struct ipa_sys_context *sys);
+
+static void ipa_replenish_rx_cache(struct ipa_sys_context *sys);
+static void ipa_replenish_rx_work_func(struct work_struct *work);
+static void ipa_wq_handle_rx(struct work_struct *work);
+static void ipa_rx_common(struct ipa_sys_context *sys, u32 size);
+static void ipa_cleanup_rx(struct ipa_sys_context *sys);
+static int ipa_poll_gsi_pkt(struct ipa_sys_context *sys);
+
+static void ipa_tx_complete(struct ipa_tx_pkt_wrapper *tx_pkt)
+{
+	struct device *dev = ipa_ctx->dev;
+
+	/* If DMA memory was mapped, unmap it */
+	if (tx_pkt->mem.virt) {
+		if (tx_pkt->type == IPA_DATA_DESC_SKB_PAGED)
+			dma_unmap_page(dev, tx_pkt->mem.phys,
+				       tx_pkt->mem.size, DMA_TO_DEVICE);
+		else
+			dma_unmap_single(dev, tx_pkt->mem.phys,
+					 tx_pkt->mem.size, DMA_TO_DEVICE);
+	}
+
+	if (tx_pkt->callback)
+		tx_pkt->callback(tx_pkt->user1, tx_pkt->user2);
+
+	kmem_cache_free(ipa_ctx->dp->tx_pkt_wrapper_cache, tx_pkt);
+}
+
+static void
+ipa_wq_write_done_common(struct ipa_sys_context *sys,
+			 struct ipa_tx_pkt_wrapper *tx_pkt)
+{
+	struct ipa_tx_pkt_wrapper *next_pkt;
+	int cnt;
+	int i;
+
+	cnt = tx_pkt->cnt;
+	for (i = 0; i < cnt; i++) {
+		ipa_assert(!list_empty(&sys->head_desc_list));
+
+		spin_lock_bh(&sys->spinlock);
+
+		next_pkt = list_next_entry(tx_pkt, link);
+		list_del(&tx_pkt->link);
+		sys->len--;
+
+		spin_unlock_bh(&sys->spinlock);
+
+		ipa_tx_complete(tx_pkt);
+
+		tx_pkt = next_pkt;
+	}
+}
+
+/**
+ * ipa_wq_write_done() - Work function executed when TX completes
+ * * @done_work:	work_struct used by the work queue
+ */
+static void ipa_wq_write_done(struct work_struct *done_work)
+{
+	struct ipa_tx_pkt_wrapper *this_pkt;
+	struct ipa_tx_pkt_wrapper *tx_pkt;
+	struct ipa_sys_context *sys;
+
+	tx_pkt = container_of(done_work, struct ipa_tx_pkt_wrapper, done_work);
+	sys = tx_pkt->sys;
+	spin_lock_bh(&sys->spinlock);
+	this_pkt = list_first_entry(&sys->head_desc_list,
+				    struct ipa_tx_pkt_wrapper, link);
+	while (tx_pkt != this_pkt) {
+		spin_unlock_bh(&sys->spinlock);
+		ipa_wq_write_done_common(sys, this_pkt);
+		spin_lock_bh(&sys->spinlock);
+		this_pkt = list_first_entry(&sys->head_desc_list,
+					    struct ipa_tx_pkt_wrapper, link);
+	}
+	spin_unlock_bh(&sys->spinlock);
+	ipa_wq_write_done_common(sys, tx_pkt);
+}
+
+/**
+ * ipa_rx_poll() - Poll the rx packets from IPA hardware
+ * @ep_id:	Endpoint to poll
+ * @weight:	NAPI poll weight
+ *
+ * Return:	The number of received packets.
+ */
+int ipa_rx_poll(u32 ep_id, int weight)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	static int total_cnt;
+	int cnt = 0;
+
+	while (cnt < weight && ipa_ep_polling(ep)) {
+		int ret;
+
+		ret = ipa_poll_gsi_pkt(ep->sys);
+		if (ret < 0)
+			break;
+
+		ipa_rx_common(ep->sys, (u32)ret);
+		cnt += IPA_WAN_AGGR_PKT_CNT;
+		total_cnt++;
+
+		/* Force switch back to interrupt mode if no more packets */
+		if (!ep->sys->len || total_cnt >= ep->sys->rx.pool_sz) {
+			total_cnt = 0;
+			cnt--;
+			break;
+		}
+	}
+
+	if (cnt < weight) {
+		ep->client_notify(ep->priv, IPA_CLIENT_COMP_NAPI, 0);
+		ipa_rx_switch_to_intr_mode(ep->sys);
+
+		/* Matching enable is in ipa_gsi_irq_rx_notify_cb() */
+		ipa_client_remove();
+	}
+
+	return cnt;
+}
+
+/**
+ * ipa_send_nop() - Send an interrupting no-op request to a producer endpoint.
+ * @sys:	System context for the endpoint
+ *
+ * Normally an interrupt is generated upon completion of every transfer
+ * performed by an endpoint, but a producer endpoint can be configured
+ * to avoid getting these interrupts.  Instead, once a transfer has been
+ * initiated, a no-op is scheduled to be sent after a short delay.  This
+ * no-op request will interrupt when it is complete, and in handling that
+ * interrupt, previously-completed transfers will be handled as well.  If
+ * a no-op is already scheduled, another is not initiated (there's only
+ * one pending at a time).
+ */
+static bool ipa_send_nop(struct ipa_sys_context *sys)
+{
+	struct gsi_xfer_elem nop_xfer = { };
+	struct ipa_tx_pkt_wrapper *nop_pkt;
+	u32 channel_id;
+
+	nop_pkt = kmem_cache_zalloc(ipa_ctx->dp->tx_pkt_wrapper_cache,
+				    GFP_KERNEL);
+	if (!nop_pkt)
+		return false;
+
+	nop_pkt->type = IPA_DATA_DESC;
+	/* No-op packet uses no memory for data */
+	INIT_WORK(&nop_pkt->done_work, ipa_wq_write_done);
+	nop_pkt->sys = sys;
+	nop_pkt->cnt = 1;
+
+	nop_xfer.type = GSI_XFER_ELEM_NOP;
+	nop_xfer.flags = GSI_XFER_FLAG_EOT;
+	nop_xfer.user_data = nop_pkt;
+
+	spin_lock_bh(&sys->spinlock);
+	list_add_tail(&nop_pkt->link, &sys->head_desc_list);
+	spin_unlock_bh(&sys->spinlock);
+
+	channel_id = sys->ep->channel_id;
+	if (!gsi_channel_queue(ipa_ctx->gsi, channel_id, 1, &nop_xfer, true))
+		return true;	/* Success */
+
+	spin_lock_bh(&sys->spinlock);
+	list_del(&nop_pkt->link);
+	spin_unlock_bh(&sys->spinlock);
+
+	kmem_cache_free(ipa_ctx->dp->tx_pkt_wrapper_cache, nop_pkt);
+
+	return false;
+}
+
+/**
+ * ipa_send_nop_work() - Work function for sending a no-op request
+ * nop_work:	Work structure for the request
+ *
+ * Try to send the no-op request.  If it fails, arrange to try again.
+ */
+static void ipa_send_nop_work(struct work_struct *nop_work)
+{
+	struct ipa_sys_context *sys;
+
+	sys = container_of(nop_work, struct ipa_sys_context, tx.nop_work);
+
+	/* If sending a no-op request fails, schedule another try */
+	if (!ipa_send_nop(sys))
+		queue_work(sys->wq, nop_work);
+}
+
+/**
+ * ipa_nop_timer_expiry() - Timer function to schedule a no-op request
+ * @timer:	High-resolution timer structure
+ *
+ * The delay before sending the no-op request is implemented by a
+ * high resolution timer, which will call this in interrupt context.
+ * Arrange to send the no-op in workqueue context when it expires.
+ */
+static enum hrtimer_restart ipa_nop_timer_expiry(struct hrtimer *timer)
+{
+	struct ipa_sys_context *sys;
+
+	sys = container_of(timer, struct ipa_sys_context, tx.nop_timer);
+	atomic_set(&sys->tx.nop_pending, 0);
+	queue_work(sys->wq, &sys->tx.nop_work);
+
+	return HRTIMER_NORESTART;
+}
+
+static void ipa_nop_timer_schedule(struct ipa_sys_context *sys)
+{
+	ktime_t time;
+
+	if (atomic_xchg(&sys->tx.nop_pending, 1))
+		return;
+
+	time = ktime_set(0, IPA_TX_NOP_DELAY_NS);
+	hrtimer_start(&sys->tx.nop_timer, time, HRTIMER_MODE_REL);
+}
+
+/**
+ * ipa_no_intr_init() - Configure endpoint point for no-op requests
+ * @prod_ep_id:	Endpoint that will use interrupting no-ops
+ *
+ * For some producer endpoints we don't interrupt on completions.
+ * Instead we schedule an interrupting NOP command to be issued on
+ * the endpoint after a short delay (if one is not already scheduled).
+ * When the NOP completes it signals all preceding transfers have
+ * completed also.
+ */
+void ipa_no_intr_init(u32 prod_ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[prod_ep_id];
+
+	INIT_WORK(&ep->sys->tx.nop_work, ipa_send_nop_work);
+	atomic_set(&ep->sys->tx.nop_pending, 0);
+	hrtimer_init(&ep->sys->tx.nop_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	ep->sys->tx.nop_timer.function = ipa_nop_timer_expiry;
+	ep->sys->tx.no_intr = true;
+}
+
+/**
+ * ipa_send() - Send descriptors to hardware as a single transaction
+ * @sys:	System context for endpoint
+ * @num_desc:	Number of descriptors
+ * @desc:	Transfer descriptors to send
+ *
+ * Return:	0 iff successful, or a negative error code.
+ */
+static int
+ipa_send(struct ipa_sys_context *sys, u32 num_desc, struct ipa_desc *desc)
+{
+	struct ipa_tx_pkt_wrapper *tx_pkt;
+	struct ipa_tx_pkt_wrapper *first;
+	struct ipa_tx_pkt_wrapper *next;
+	struct gsi_xfer_elem *xfer_elem;
+	LIST_HEAD(pkt_list);
+	int ret;
+	int i;
+
+	ipa_assert(num_desc);
+	ipa_assert(num_desc <= ipa_client_tlv_count(sys->ep->client));
+
+	xfer_elem = kcalloc(num_desc, sizeof(*xfer_elem), GFP_ATOMIC);
+	if (!xfer_elem)
+		return -ENOMEM;
+
+	/* Within loop, all errors are allocation or DMA mapping */
+	ret = -ENOMEM;
+	first = NULL;
+	for (i = 0; i < num_desc; i++) {
+		dma_addr_t phys;
+
+		tx_pkt = kmem_cache_zalloc(ipa_ctx->dp->tx_pkt_wrapper_cache,
+					   GFP_ATOMIC);
+		if (!tx_pkt)
+			goto err_unwind;
+
+		if (!first)
+			first = tx_pkt;
+
+		if (desc[i].type == IPA_DATA_DESC_SKB_PAGED)
+			phys = skb_frag_dma_map(ipa_ctx->dev, desc[i].payload,
+						0, desc[i].len_opcode,
+						DMA_TO_DEVICE);
+		else
+			phys = dma_map_single(ipa_ctx->dev, desc[i].payload,
+					      desc[i].len_opcode,
+					      DMA_TO_DEVICE);
+		if (dma_mapping_error(ipa_ctx->dev, phys)) {
+			ipa_err("dma mapping error on descriptor\n");
+			kmem_cache_free(ipa_ctx->dp->tx_pkt_wrapper_cache,
+					tx_pkt);
+			goto err_unwind;
+		}
+
+		tx_pkt->type = desc[i].type;
+		tx_pkt->sys = sys;
+		tx_pkt->mem.virt = desc[i].payload;
+		tx_pkt->mem.phys = phys;
+		tx_pkt->mem.size = desc[i].len_opcode;
+		tx_pkt->callback = desc[i].callback;
+		tx_pkt->user1 = desc[i].user1;
+		tx_pkt->user2 = desc[i].user2;
+		list_add_tail(&tx_pkt->link, &pkt_list);
+
+		xfer_elem[i].addr = tx_pkt->mem.phys;
+		if (desc[i].type == IPA_IMM_CMD_DESC)
+			xfer_elem[i].type = GSI_XFER_ELEM_IMME_CMD;
+		else
+			xfer_elem[i].type = GSI_XFER_ELEM_DATA;
+		xfer_elem[i].len_opcode = desc[i].len_opcode;
+		if (i < num_desc - 1)
+			xfer_elem[i].flags = GSI_XFER_FLAG_CHAIN;
+	}
+
+	/* Fill in extra fields in the first TX packet */
+	first->cnt = num_desc;
+	INIT_WORK(&first->done_work, ipa_wq_write_done);
+
+	/* Fill in extra fields in the last transfer element */
+	if (!sys->tx.no_intr) {
+		xfer_elem[num_desc - 1].flags = GSI_XFER_FLAG_EOT;
+		xfer_elem[num_desc - 1].flags |= GSI_XFER_FLAG_BEI;
+	}
+	xfer_elem[num_desc - 1].user_data = first;
+
+	spin_lock_bh(&sys->spinlock);
+
+	list_splice_tail_init(&pkt_list, &sys->head_desc_list);
+	ret = gsi_channel_queue(ipa_ctx->gsi, sys->ep->channel_id, num_desc,
+				xfer_elem, true);
+	if (ret)
+		list_cut_end(&pkt_list, &sys->head_desc_list, &first->link);
+
+	spin_unlock_bh(&sys->spinlock);
+
+	kfree(xfer_elem);
+
+	if (!ret) {
+		if (sys->tx.no_intr)
+			ipa_nop_timer_schedule(sys);
+		return 0;
+	}
+err_unwind:
+	list_for_each_entry_safe(tx_pkt, next, &pkt_list, link) {
+		list_del(&tx_pkt->link);
+		tx_pkt->callback = NULL; /* Avoid doing the callback */
+		ipa_tx_complete(tx_pkt);
+	}
+
+	return ret;
+}
+
+/**
+ * ipa_send_cmd_timeout_complete() - Command completion callback
+ * @user1:	Opaque value carried by the command
+ * @ignored:	Second opaque value (ignored)
+ *
+ * Schedule a completion to signal that a command is done.  Free the
+ * tag_completion structure if its reference count reaches zero.
+ */
+static void ipa_send_cmd_timeout_complete(void *user1, int ignored)
+{
+	struct ipa_tag_completion *comp = user1;
+
+	complete(&comp->comp);
+	if (!atomic_dec_return(&comp->cnt))
+		kfree(comp);
+}
+
+/**
+ * ipa_send_cmd_timeout() - Send an immediate command with timeout
+ * @desc:	descriptor structure
+ * @timeout:	milliseconds to wait (or 0 to wait indefinitely)
+ *
+ * Send an immediate command, and wait for it to complete.  If
+ * timeout is non-zero it indicates the number of milliseconds to
+ * wait to receive the acknowledgment from the hardware before
+ * timing out.  If 0 is supplied, wait will not time out.
+ *
+ * Return:	0 if successful, or a negative error code
+ */
+int ipa_send_cmd_timeout(struct ipa_desc *desc, u32 timeout)
+{
+	struct ipa_tag_completion *comp;
+	unsigned long timeout_jiffies;
+	struct ipa_ep_context *ep;
+	int ret;
+
+	comp = kzalloc(sizeof(*comp), GFP_KERNEL);
+	if (!comp)
+		return -ENOMEM;
+
+	/* The reference count is decremented both here and in ack
+	 * callback.  Whichever reaches 0 frees the structure.
+	 */
+	atomic_set(&comp->cnt, 2);
+	init_completion(&comp->comp);
+
+	/* Fill in the callback info (the sole descriptor is the last) */
+	desc->callback = ipa_send_cmd_timeout_complete;
+	desc->user1 = comp;
+
+	ep = &ipa_ctx->ep[ipa_client_ep_id(IPA_CLIENT_APPS_CMD_PROD)];
+	ret = ipa_send(ep->sys, 1, desc);
+	if (ret) {
+		/* Callback won't run; drop reference on its behalf */
+		atomic_dec(&comp->cnt);
+		goto out;
+	}
+
+	timeout_jiffies = msecs_to_jiffies(timeout);
+	if (!timeout_jiffies) {
+		wait_for_completion(&comp->comp);
+	} else if (!wait_for_completion_timeout(&comp->comp, timeout_jiffies)) {
+		ret = -ETIMEDOUT;
+		ipa_err("command timed out\n");
+	}
+out:
+	if (!atomic_dec_return(&comp->cnt))
+		kfree(comp);
+
+	return ret;
+}
+
+/**
+ * ipa_handle_rx_core() - Core packet reception handling
+ * @sys:	System context for endpoint receiving packets
+ *
+ * Return:	The number of packets processed, or a negative error code
+ */
+static int ipa_handle_rx_core(struct ipa_sys_context *sys)
+{
+	int cnt;
+
+	/* Stop if the endpoint leaves polling state */
+	cnt = 0;
+	while (ipa_ep_polling(sys->ep)) {
+		int ret = ipa_poll_gsi_pkt(sys);
+
+		if (ret < 0)
+			break;
+
+		ipa_rx_common(sys, (u32)ret);
+
+		cnt++;
+	}
+
+	return cnt;
+}
+
+/**
+ * ipa_rx_switch_to_intr_mode() - Switch from polling to interrupt mode
+ * @sys:	System context for endpoint switching mode
+ */
+static void ipa_rx_switch_to_intr_mode(struct ipa_sys_context *sys)
+{
+	if (!atomic_xchg(&sys->rx.curr_polling_state, 0)) {
+		ipa_err("already in intr mode\n");
+		queue_delayed_work(sys->wq, &sys->rx.switch_to_intr_work,
+				   msecs_to_jiffies(1));
+		return;
+	}
+	ipa_dec_release_wakelock();
+	gsi_channel_intr_enable(ipa_ctx->gsi, sys->ep->channel_id);
+}
+
+void ipa_rx_switch_to_poll_mode(struct ipa_sys_context *sys)
+{
+	if (atomic_xchg(&sys->rx.curr_polling_state, 1))
+		return;
+	gsi_channel_intr_disable(ipa_ctx->gsi, sys->ep->channel_id);
+	ipa_inc_acquire_wakelock();
+	queue_work(sys->wq, &sys->rx.work);
+}
+
+/**
+ * ipa_handle_rx() - Handle packet reception.
+ * @sys:	System context for endpoint receiving packets
+ */
+static void ipa_handle_rx(struct ipa_sys_context *sys)
+{
+	int inactive_cycles = 0;
+	int cnt;
+
+	ipa_client_add();
+	do {
+		cnt = ipa_handle_rx_core(sys);
+		if (cnt == 0)
+			inactive_cycles++;
+		else
+			inactive_cycles = 0;
+
+		usleep_range(POLLING_MIN_SLEEP_RX, POLLING_MAX_SLEEP_RX);
+
+		/* if endpoint is out of buffers there is no point polling for
+		 * completed descs; release the worker so delayed work can
+		 * run in a timely manner
+		 */
+		if (sys->len - sys->rx.len_pending_xfer == 0)
+			break;
+
+	} while (inactive_cycles <= POLLING_INACTIVITY_RX);
+
+	ipa_rx_switch_to_intr_mode(sys);
+	ipa_client_remove();
+}
+
+static void ipa_switch_to_intr_rx_work_func(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct ipa_sys_context *sys;
+
+	sys = container_of(dwork, struct ipa_sys_context,
+			   rx.switch_to_intr_work);
+
+	/* For NAPI, interrupt mode is done in ipa_rx_poll context */
+	ipa_assert(!sys->ep->napi_enabled);
+
+	ipa_handle_rx(sys);
+}
+
+static struct ipa_sys_context *ipa_ep_sys_create(enum ipa_client_type client)
+{
+	const unsigned int wq_flags = WQ_MEM_RECLAIM | WQ_UNBOUND;
+	struct ipa_sys_context *sys;
+
+	/* Caller will zero all "mutable" fields; we fill in the rest */
+	sys = kmalloc(sizeof(*sys), GFP_KERNEL);
+	if (!sys)
+		return NULL;
+
+	sys->wq = alloc_workqueue("ipawq%u", wq_flags, 1, (u32)client);
+	if (!sys->wq) {
+		kfree(sys);
+		return NULL;
+	}
+
+	/* Caller assigns sys->ep = ep */
+	INIT_LIST_HEAD(&sys->head_desc_list);
+	spin_lock_init(&sys->spinlock);
+
+	return sys;
+}
+
+/**
+ * ipa_tx_dp_complete() - Transmit complete callback
+ * @user1:	Caller-supplied pointer value
+ * @user2:	Caller-supplied integer value
+ *
+ * Calls the endpoint's client_notify function if it exists;
+ * otherwise just frees the socket buffer (supplied in user1).
+ */
+static void ipa_tx_dp_complete(void *user1, int user2)
+{
+	struct sk_buff *skb = user1;
+	int ep_id = user2;
+
+	if (ipa_ctx->ep[ep_id].client_notify) {
+		unsigned long data;
+		void *priv;
+
+		priv = ipa_ctx->ep[ep_id].priv;
+		data = (unsigned long)skb;
+		ipa_ctx->ep[ep_id].client_notify(priv, IPA_WRITE_DONE, data);
+	} else {
+		dev_kfree_skb_any(skb);
+	}
+}
+
+/**
+ * ipa_tx_dp() - Transmit a socket buffer for APPS_WAN_PROD
+ * @client:	IPA client that is sending packets (WAN producer)
+ * @skb:	The socket buffer to send
+ *
+ * Returns:	0 if successful, or a negative error code
+ */
+int ipa_tx_dp(enum ipa_client_type client, struct sk_buff *skb)
+{
+	struct ipa_desc _desc = { };	/* Used for common case */
+	struct ipa_desc *desc;
+	u32 tlv_count;
+	int data_idx;
+	u32 nr_frags;
+	u32 ep_id;
+	int ret;
+	u32 f;
+
+	if (!skb->len)
+		return -EINVAL;
+
+	ep_id = ipa_client_ep_id(client);
+
+	/* Make sure source endpoint's TLV FIFO has enough entries to
+	 * hold the linear portion of the skb and all its frags.
+	 * If not, see if we can linearize it before giving up.
+	 */
+	nr_frags = skb_shinfo(skb)->nr_frags;
+	tlv_count = ipa_client_tlv_count(client);
+	if (1 + nr_frags > tlv_count) {
+		if (skb_linearize(skb))
+			return -ENOMEM;
+		nr_frags = 0;
+	}
+	if (nr_frags) {
+		desc = kcalloc(1 + nr_frags, sizeof(*desc), GFP_ATOMIC);
+		if (!desc)
+			return -ENOMEM;
+	} else {
+		desc = &_desc;	/* Default, linear case */
+	}
+
+	/* Fill in the IPA request descriptors--one for the linear
+	 * data in the skb, one each for each of its fragments.
+	 */
+	data_idx = 0;
+	desc[data_idx].payload = skb->data;
+	desc[data_idx].len_opcode = skb_headlen(skb);
+	desc[data_idx].type = IPA_DATA_DESC_SKB;
+	for (f = 0; f < nr_frags; f++) {
+		data_idx++;
+		desc[data_idx].payload = &skb_shinfo(skb)->frags[f];
+		desc[data_idx].type = IPA_DATA_DESC_SKB_PAGED;
+		desc[data_idx].len_opcode =
+				skb_frag_size(desc[data_idx].payload);
+	}
+
+	/* Have the skb be freed after the last descriptor completes. */
+	desc[data_idx].callback = ipa_tx_dp_complete;
+	desc[data_idx].user1 = skb;
+	desc[data_idx].user2 = ep_id;
+
+	ret = ipa_send(ipa_ctx->ep[ep_id].sys, data_idx + 1, desc);
+
+	if (nr_frags)
+		kfree(desc);
+
+	return ret;
+}
+
+static void ipa_wq_handle_rx(struct work_struct *work)
+{
+	struct ipa_sys_context *sys;
+
+	sys = container_of(work, struct ipa_sys_context, rx.work);
+
+	if (sys->ep->napi_enabled) {
+		ipa_client_add();
+		sys->ep->client_notify(sys->ep->priv, IPA_CLIENT_START_POLL, 0);
+	} else {
+		ipa_handle_rx(sys);
+	}
+}
+
+static int
+queue_rx_cache(struct ipa_sys_context *sys, struct ipa_rx_pkt_wrapper *rx_pkt)
+{
+	struct gsi_xfer_elem gsi_xfer_elem;
+	bool ring_doorbell;
+	int ret;
+
+	/* Don't bother zeroing this; we fill all fields */
+	gsi_xfer_elem.addr = rx_pkt->dma_addr;
+	gsi_xfer_elem.len_opcode = sys->rx.buff_sz;
+	gsi_xfer_elem.flags = GSI_XFER_FLAG_EOT;
+	gsi_xfer_elem.flags |= GSI_XFER_FLAG_EOB;
+	gsi_xfer_elem.type = GSI_XFER_ELEM_DATA;
+	gsi_xfer_elem.user_data = rx_pkt;
+
+	/* Doorbell is expensive; only ring it when a batch is queued */
+	ring_doorbell = sys->rx.len_pending_xfer++ >= IPA_REPL_XFER_THRESH;
+
+	ret = gsi_channel_queue(ipa_ctx->gsi, sys->ep->channel_id,
+				1, &gsi_xfer_elem, ring_doorbell);
+	if (ret)
+		return ret;
+
+	if (ring_doorbell)
+		sys->rx.len_pending_xfer = 0;
+
+	return 0;
+}
+
+/**
+ * ipa_replenish_rx_cache() - Replenish the Rx packets cache.
+ * @sys:	System context for IPA->AP endpoint
+ *
+ * Allocate RX packet wrapper structures with maximal socket buffers
+ * for an endpoint.  These are supplied to the hardware, which fills
+ * them with incoming data.
+ */
+static void ipa_replenish_rx_cache(struct ipa_sys_context *sys)
+{
+	struct ipa_rx_pkt_wrapper *rx_pkt;
+	struct device *dev = ipa_ctx->dev;
+	u32 rx_len_cached = sys->len;
+
+	while (rx_len_cached < sys->rx.pool_sz) {
+		gfp_t flag = GFP_NOWAIT | __GFP_NOWARN;
+		void *ptr;
+		int ret;
+
+		rx_pkt = kmem_cache_zalloc(ipa_ctx->dp->rx_pkt_wrapper_cache,
+					   flag);
+		if (!rx_pkt)
+			goto fail_kmem_cache_alloc;
+
+		INIT_LIST_HEAD(&rx_pkt->link);
+
+		rx_pkt->skb = __dev_alloc_skb(sys->rx.buff_sz, flag);
+		if (!rx_pkt->skb) {
+			ipa_err("failed to alloc skb\n");
+			goto fail_skb_alloc;
+		}
+		ptr = skb_put(rx_pkt->skb, sys->rx.buff_sz);
+		rx_pkt->dma_addr = dma_map_single(dev, ptr, sys->rx.buff_sz,
+						  DMA_FROM_DEVICE);
+		if (dma_mapping_error(dev, rx_pkt->dma_addr)) {
+			ipa_err("dma_map_single failure %p for %p\n",
+				(void *)rx_pkt->dma_addr, ptr);
+			goto fail_dma_mapping;
+		}
+
+		list_add_tail(&rx_pkt->link, &sys->head_desc_list);
+		rx_len_cached = ++sys->len;
+
+		ret = queue_rx_cache(sys, rx_pkt);
+		if (ret)
+			goto fail_provide_rx_buffer;
+	}
+
+	return;
+
+fail_provide_rx_buffer:
+	list_del(&rx_pkt->link);
+	rx_len_cached = --sys->len;
+	dma_unmap_single(dev, rx_pkt->dma_addr, sys->rx.buff_sz,
+			 DMA_FROM_DEVICE);
+fail_dma_mapping:
+	dev_kfree_skb_any(rx_pkt->skb);
+fail_skb_alloc:
+	kmem_cache_free(ipa_ctx->dp->rx_pkt_wrapper_cache, rx_pkt);
+fail_kmem_cache_alloc:
+	if (rx_len_cached - sys->rx.len_pending_xfer == 0)
+		queue_delayed_work(sys->wq, &sys->rx.replenish_work,
+				   msecs_to_jiffies(1));
+}
+
+static void ipa_replenish_rx_work_func(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct ipa_sys_context *sys;
+
+	sys = container_of(dwork, struct ipa_sys_context, rx.replenish_work);
+	ipa_client_add();
+	ipa_replenish_rx_cache(sys);
+	ipa_client_remove();
+}
+
+/** ipa_cleanup_rx() - release RX queue resources */
+static void ipa_cleanup_rx(struct ipa_sys_context *sys)
+{
+	struct ipa_rx_pkt_wrapper *rx_pkt;
+	struct ipa_rx_pkt_wrapper *r;
+
+	list_for_each_entry_safe(rx_pkt, r, &sys->head_desc_list, link) {
+		list_del(&rx_pkt->link);
+		dma_unmap_single(ipa_ctx->dev, rx_pkt->dma_addr,
+				 sys->rx.buff_sz, DMA_FROM_DEVICE);
+		dev_kfree_skb_any(rx_pkt->skb);
+		kmem_cache_free(ipa_ctx->dp->rx_pkt_wrapper_cache, rx_pkt);
+	}
+}
+
+static struct sk_buff *ipa_skb_copy_for_client(struct sk_buff *skb, int len)
+{
+	struct sk_buff *skb2;
+
+	skb2 = __dev_alloc_skb(len + IPA_RX_BUFF_CLIENT_HEADROOM, GFP_KERNEL);
+	if (likely(skb2)) {
+		/* Set the data pointer */
+		skb_reserve(skb2, IPA_RX_BUFF_CLIENT_HEADROOM);
+		memcpy(skb2->data, skb->data, len);
+		skb2->len = len;
+		skb_set_tail_pointer(skb2, len);
+	}
+
+	return skb2;
+}
+
+static struct sk_buff *ipa_join_prev_skb(struct sk_buff *prev_skb,
+					 struct sk_buff *skb, unsigned int len)
+{
+	struct sk_buff *skb2;
+
+	skb2 = skb_copy_expand(prev_skb, 0, len, GFP_KERNEL);
+	if (likely(skb2))
+		memcpy(skb_put(skb2, len), skb->data, len);
+	else
+		ipa_err("copy expand failed\n");
+	dev_kfree_skb_any(prev_skb);
+
+	return skb2;
+}
+
+static bool ipa_status_opcode_supported(enum ipahal_pkt_status_opcode opcode)
+{
+	return opcode == IPAHAL_PKT_STATUS_OPCODE_PACKET ||
+		opcode == IPAHAL_PKT_STATUS_OPCODE_DROPPED_PACKET ||
+		opcode == IPAHAL_PKT_STATUS_OPCODE_SUSPENDED_PACKET ||
+		opcode == IPAHAL_PKT_STATUS_OPCODE_PACKET_2ND_PASS;
+}
+
+static void
+ipa_lan_rx_pyld_hdlr(struct sk_buff *skb, struct ipa_sys_context *sys)
+{
+	struct ipahal_pkt_status status;
+	struct sk_buff *skb2;
+	unsigned long unused;
+	unsigned int align;
+	unsigned int used;
+	unsigned char *buf;
+	u32 pkt_status_sz;
+	int pad_len_byte;
+	u32 ep_id;
+	int len;
+	int len2;
+
+	pkt_status_sz = ipahal_pkt_status_get_size();
+	used = *(unsigned int *)skb->cb;
+	align = ALIGN(used, 32);
+	unused = IPA_RX_BUFFER_SIZE - used;
+
+	ipa_assert(skb->len);
+
+	if (sys->rx.len_partial) {
+		buf = skb_push(skb, sys->rx.len_partial);
+		memcpy(buf, sys->rx.prev_skb->data, sys->rx.len_partial);
+		sys->rx.len_partial = 0;
+		dev_kfree_skb_any(sys->rx.prev_skb);
+		sys->rx.prev_skb = NULL;
+		goto begin;
+	}
+
+	/* this endpoint has TX comp (status only) + mux-ed LAN RX data
+	 * (status+data)
+	 */
+	if (sys->rx.len_rem) {
+		if (sys->rx.len_rem <= skb->len) {
+			if (sys->rx.prev_skb) {
+				skb2 = skb_copy_expand(sys->rx.prev_skb, 0,
+						       sys->rx.len_rem,
+						       GFP_KERNEL);
+				if (likely(skb2)) {
+					memcpy(skb_put(skb2, sys->rx.len_rem),
+					       skb->data, sys->rx.len_rem);
+					skb_trim(skb2,
+						 skb2->len - sys->rx.len_pad);
+					skb2->truesize = skb2->len +
+						sizeof(struct sk_buff);
+					if (sys->rx.drop_packet)
+						dev_kfree_skb_any(skb2);
+					else
+						sys->ep->client_notify(
+							sys->ep->priv,
+							IPA_RECEIVE,
+							(unsigned long)(skb2));
+				} else {
+					ipa_err("copy expand failed\n");
+				}
+				dev_kfree_skb_any(sys->rx.prev_skb);
+			}
+			skb_pull(skb, sys->rx.len_rem);
+			sys->rx.prev_skb = NULL;
+			sys->rx.len_rem = 0;
+			sys->rx.len_pad = 0;
+		} else {
+			if (sys->rx.prev_skb) {
+				skb2 = ipa_join_prev_skb(sys->rx.prev_skb, skb,
+							 skb->len);
+				dev_kfree_skb_any(sys->rx.prev_skb);
+				sys->rx.prev_skb = skb2;
+			}
+			sys->rx.len_rem -= skb->len;
+			return;
+		}
+	}
+
+begin:
+	while (skb->len) {
+		sys->rx.drop_packet = false;
+
+		if (skb->len < pkt_status_sz) {
+			WARN_ON(sys->rx.prev_skb);
+			sys->rx.prev_skb = skb_copy(skb, GFP_KERNEL);
+			sys->rx.len_partial = skb->len;
+			return;
+		}
+
+		ipahal_pkt_status_parse(skb->data, &status);
+
+		if (!ipa_status_opcode_supported(status.status_opcode)) {
+			ipa_err("unsupported opcode(%d)\n",
+				status.status_opcode);
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+
+		if (status.pkt_len == 0) {
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+
+		if (status.endp_dest_idx == (sys->ep - ipa_ctx->ep)) {
+			/* RX data */
+			ep_id = status.endp_src_idx;
+
+			/* A packet which is received back to the AP after
+			 * there was no route match.
+			 */
+
+			if (status.exception ==
+				IPAHAL_PKT_STATUS_EXCEPTION_NONE &&
+			    status.rt_miss)
+				sys->rx.drop_packet = true;
+			if (skb->len == pkt_status_sz &&
+			    status.exception ==
+					IPAHAL_PKT_STATUS_EXCEPTION_NONE) {
+				WARN_ON(sys->rx.prev_skb);
+				sys->rx.prev_skb = skb_copy(skb, GFP_KERNEL);
+				sys->rx.len_partial = skb->len;
+				return;
+			}
+
+			pad_len_byte = ((status.pkt_len + 3) & ~3) -
+					status.pkt_len;
+
+			len = status.pkt_len + pad_len_byte +
+				IPA_SIZE_DL_CSUM_META_TRAILER;
+
+			if (status.exception ==
+					IPAHAL_PKT_STATUS_EXCEPTION_DEAGGR) {
+				sys->rx.drop_packet = true;
+			}
+
+			len2 = min(status.pkt_len + pkt_status_sz, skb->len);
+			skb2 = ipa_skb_copy_for_client(skb, len2);
+			if (likely(skb2)) {
+				if (skb->len < len + pkt_status_sz) {
+					sys->rx.prev_skb = skb2;
+					sys->rx.len_rem = len - skb->len +
+						pkt_status_sz;
+					sys->rx.len_pad = pad_len_byte;
+					skb_pull(skb, skb->len);
+				} else {
+					skb_trim(skb2, status.pkt_len +
+							pkt_status_sz);
+					if (sys->rx.drop_packet) {
+						dev_kfree_skb_any(skb2);
+					} else {
+						skb2->truesize =
+							skb2->len +
+							sizeof(struct sk_buff) +
+							(ALIGN(len +
+							pkt_status_sz, 32) *
+							unused / align);
+						sys->ep->client_notify(
+							sys->ep->priv,
+							IPA_RECEIVE,
+							(unsigned long)(skb2));
+					}
+					skb_pull(skb, len + pkt_status_sz);
+				}
+			} else {
+				ipa_err("fail to alloc skb\n");
+				if (skb->len < len) {
+					sys->rx.prev_skb = NULL;
+					sys->rx.len_rem = len - skb->len +
+						pkt_status_sz;
+					sys->rx.len_pad = pad_len_byte;
+					skb_pull(skb, skb->len);
+				} else {
+					skb_pull(skb, len + pkt_status_sz);
+				}
+			}
+		} else {
+			skb_pull(skb, pkt_status_sz);
+		}
+	}
+}
+
+static void
+ipa_wan_rx_handle_splt_pyld(struct sk_buff *skb, struct ipa_sys_context *sys)
+{
+	struct sk_buff *skb2;
+
+	if (sys->rx.len_rem <= skb->len) {
+		if (sys->rx.prev_skb) {
+			skb2 = ipa_join_prev_skb(sys->rx.prev_skb, skb,
+						 sys->rx.len_rem);
+			if (likely(skb2)) {
+				skb_pull(skb2, ipahal_pkt_status_get_size());
+				skb2->truesize = skb2->len +
+					sizeof(struct sk_buff);
+				sys->ep->client_notify(sys->ep->priv,
+						       IPA_RECEIVE,
+						       (unsigned long)skb2);
+			}
+		}
+		skb_pull(skb, sys->rx.len_rem);
+		sys->rx.prev_skb = NULL;
+		sys->rx.len_rem = 0;
+	} else {
+		if (sys->rx.prev_skb) {
+			skb2 = ipa_join_prev_skb(sys->rx.prev_skb, skb,
+						 skb->len);
+			sys->rx.prev_skb = skb2;
+		}
+		sys->rx.len_rem -= skb->len;
+		skb_pull(skb, skb->len);
+	}
+}
+
+static void
+ipa_wan_rx_pyld_hdlr(struct sk_buff *skb, struct ipa_sys_context *sys)
+{
+	struct ipahal_pkt_status status;
+	unsigned char *skb_data;
+	struct sk_buff *skb2;
+	u16 pkt_len_with_pad;
+	unsigned long unused;
+	unsigned int align;
+	unsigned int used;
+	u32 pkt_status_sz;
+	int frame_len;
+	u32 qmap_hdr;
+	int checksum;
+	int ep_id;
+
+	used = *(unsigned int *)skb->cb;
+	align = ALIGN(used, 32);
+	unused = IPA_RX_BUFFER_SIZE - used;
+
+	ipa_assert(skb->len);
+
+	if (ipa_ctx->ipa_client_apps_wan_cons_agg_gro) {
+		sys->ep->client_notify(sys->ep->priv, IPA_RECEIVE,
+				       (unsigned long)(skb));
+		return;
+	}
+
+	/* payload splits across 2 buff or more,
+	 * take the start of the payload from rx.prev_skb
+	 */
+	if (sys->rx.len_rem)
+		ipa_wan_rx_handle_splt_pyld(skb, sys);
+
+	pkt_status_sz = ipahal_pkt_status_get_size();
+	while (skb->len) {
+		u32 status_mask;
+
+		if (skb->len < pkt_status_sz) {
+			ipa_err("status straddles buffer\n");
+			WARN_ON(1);
+			goto bail;
+		}
+		ipahal_pkt_status_parse(skb->data, &status);
+		skb_data = skb->data;
+
+		if (!ipa_status_opcode_supported(status.status_opcode) ||
+		    status.status_opcode ==
+				IPAHAL_PKT_STATUS_OPCODE_SUSPENDED_PACKET) {
+			ipa_err("unsupported opcode(%d)\n",
+				status.status_opcode);
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+
+		if (status.endp_dest_idx >= ipa_ctx->ep_count ||
+		    status.endp_src_idx >= ipa_ctx->ep_count ||
+		    status.pkt_len > IPA_GENERIC_AGGR_BYTE_LIMIT) {
+			ipa_err("status fields invalid\n");
+			WARN_ON(1);
+			goto bail;
+		}
+		if (status.pkt_len == 0) {
+			skb_pull(skb, pkt_status_sz);
+			continue;
+		}
+		ep_id = ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS);
+		if (status.endp_dest_idx != ep_id) {
+			ipa_err("expected endp_dest_idx %d received %d\n",
+				ep_id, status.endp_dest_idx);
+			WARN_ON(1);
+			goto bail;
+		}
+		/* RX data */
+		if (skb->len == pkt_status_sz) {
+			ipa_err("Ins header in next buffer\n");
+			WARN_ON(1);
+			goto bail;
+		}
+		qmap_hdr = *(u32 *)(skb_data + pkt_status_sz);
+
+		/* Take the pkt_len_with_pad from the last 2 bytes of the QMAP
+		 * header
+		 */
+		/*QMAP is BE: convert the pkt_len field from BE to LE*/
+		pkt_len_with_pad = ntohs((qmap_hdr >> 16) & 0xffff);
+		/*get the CHECKSUM_PROCESS bit*/
+		status_mask = status.status_mask;
+		checksum = status_mask & IPAHAL_PKT_STATUS_MASK_CKSUM_PROCESS;
+
+		frame_len = pkt_status_sz + IPA_QMAP_HEADER_LENGTH +
+			    pkt_len_with_pad;
+		if (checksum)
+			frame_len += IPA_DL_CHECKSUM_LENGTH;
+
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (likely(skb2)) {
+			/* the len of actual data is smaller than expected
+			 * payload split across 2 buff
+			 */
+			if (skb->len < frame_len) {
+				sys->rx.prev_skb = skb2;
+				sys->rx.len_rem = frame_len - skb->len;
+				skb_pull(skb, skb->len);
+			} else {
+				skb_trim(skb2, frame_len);
+				skb_pull(skb2, pkt_status_sz);
+				skb2->truesize = skb2->len +
+					sizeof(struct sk_buff) +
+					(ALIGN(frame_len, 32) *
+					 unused / align);
+				sys->ep->client_notify(sys->ep->priv,
+						       IPA_RECEIVE,
+						       (unsigned long)(skb2));
+				skb_pull(skb, frame_len);
+			}
+		} else {
+			ipa_err("fail to clone\n");
+			if (skb->len < frame_len) {
+				sys->rx.prev_skb = NULL;
+				sys->rx.len_rem = frame_len - skb->len;
+				skb_pull(skb, skb->len);
+			} else {
+				skb_pull(skb, frame_len);
+			}
+		}
+	}
+bail:
+	dev_kfree_skb_any(skb);
+}
+
+void ipa_lan_rx_cb(void *priv, enum ipa_dp_evt_type evt, unsigned long data)
+{
+	struct sk_buff *rx_skb = (struct sk_buff *)data;
+	struct ipahal_pkt_status status;
+	struct ipa_ep_context *ep;
+	u32 pkt_status_size;
+	u32 metadata;
+	u32 ep_id;
+
+	pkt_status_size = ipahal_pkt_status_get_size();
+
+	ipa_assert(rx_skb->len >= pkt_status_size);
+
+	ipahal_pkt_status_parse(rx_skb->data, &status);
+	ep_id = status.endp_src_idx;
+	metadata = status.metadata;
+	ep = &ipa_ctx->ep[ep_id];
+	if (ep_id >= ipa_ctx->ep_count || !ep->allocated ||
+	    !ep->client_notify) {
+		ipa_err("drop endpoint=%u allocated=%s client_notify=%p\n",
+			ep_id, ep->allocated ? "true" : "false",
+			ep->client_notify);
+		dev_kfree_skb_any(rx_skb);
+		return;
+	}
+
+	/* Consume the status packet, and if no exception, the header */
+	skb_pull(rx_skb, pkt_status_size);
+	if (status.exception == IPAHAL_PKT_STATUS_EXCEPTION_NONE)
+		skb_pull(rx_skb, IPA_LAN_RX_HEADER_LENGTH);
+
+	/* Metadata Info
+	 *  ------------------------------------------
+	 *  |	3     |	  2	|    1	      |	 0   |
+	 *  | fw_desc | vdev_id | qmap mux id | Resv |
+	 *  ------------------------------------------
+	 */
+	*(u16 *)rx_skb->cb = ((metadata >> 16) & 0xffff);
+
+	ep->client_notify(ep->priv, IPA_RECEIVE, (unsigned long)rx_skb);
+}
+
+static void ipa_rx_common(struct ipa_sys_context *sys, u32 size)
+{
+	struct ipa_rx_pkt_wrapper *rx_pkt;
+	struct sk_buff *rx_skb;
+
+	ipa_assert(!list_empty(&sys->head_desc_list));
+
+	spin_lock_bh(&sys->spinlock);
+
+	rx_pkt = list_first_entry(&sys->head_desc_list,
+				  struct ipa_rx_pkt_wrapper, link);
+	list_del(&rx_pkt->link);
+	sys->len--;
+
+	spin_unlock_bh(&sys->spinlock);
+
+	rx_skb = rx_pkt->skb;
+	dma_unmap_single(ipa_ctx->dev, rx_pkt->dma_addr, sys->rx.buff_sz,
+			 DMA_FROM_DEVICE);
+
+	skb_trim(rx_skb, size);
+
+	*(unsigned int *)rx_skb->cb = rx_skb->len;
+	rx_skb->truesize = size + sizeof(struct sk_buff);
+
+	sys->rx.pyld_hdlr(rx_skb, sys);
+	kmem_cache_free(ipa_ctx->dp->rx_pkt_wrapper_cache, rx_pkt);
+	ipa_replenish_rx_cache(sys);
+}
+
+/**
+ * ipa_aggr_byte_limit_buf_size()
+ * @byte_limit:	Desired limit (in bytes) for aggregation
+ *
+ * Compute the buffer size required to support a requested aggregation
+ * byte limit.  Aggregration will close when *more* than the configured
+ * number of bytes have been added to an aggregation frame.  Our
+ * buffers therefore need to to be big enough to receive one complete
+ * packet once the configured byte limit has been consumed.
+ *
+ * An incoming packet can have as much as IPA_MTU of data in it, but
+ * the buffer also needs to be large enough to accomodate the standard
+ * socket buffer overhead (NET_SKB_PAD of headroom, plus an implied
+ * skb_shared_info structure at the end).
+ *
+ * So we compute the required buffer size by adding the standard
+ * socket buffer overhead and MTU to the requested size.  We round
+ * that down to a power of 2 in an effort to avoid fragmentation due
+ * to unaligned buffer sizes.
+ *
+ * After accounting for all of this, we return the number of bytes
+ * of buffer space the IPA hardware will know is available to hold
+ * received data (without any overhead).
+ *
+ * Return:	The computes size of buffer space available
+ */
+u32 ipa_aggr_byte_limit_buf_size(u32 byte_limit)
+{
+	/* Account for one additional packet, including overhead */
+	byte_limit += IPA_RX_BUFFER_RESERVED;
+	byte_limit += IPA_MTU;
+
+	/* Convert this size to a nearby power-of-2.  We choose one
+	 * that's *less than* the limit we seek--so we start by
+	 * subracting 1.  The highest set bit in that is used to
+	 * compute the power of 2.
+	 *
+	 * XXX Why is this *less than* and not possibly equal?
+	 */
+	byte_limit = 1 << __fls(byte_limit - 1);
+
+	/* Given that size, figure out how much buffer space that
+	 * leaves us for received data.
+	 */
+	return IPA_RX_BUFFER_AVAILABLE(byte_limit);
+}
+
+void ipa_gsi_irq_tx_notify_cb(void *xfer_data)
+{
+	struct ipa_tx_pkt_wrapper *tx_pkt = xfer_data;
+
+	queue_work(tx_pkt->sys->wq, &tx_pkt->done_work);
+}
+
+void ipa_gsi_irq_rx_notify_cb(void *chan_data, u16 count)
+{
+	struct ipa_sys_context *sys = chan_data;
+
+	sys->ep->bytes_xfered_valid = true;
+	sys->ep->bytes_xfered = count;
+
+	ipa_rx_switch_to_poll_mode(sys);
+}
+
+static int ipa_gsi_setup_channel(struct ipa_ep_context *ep, u32 channel_count,
+				 u32 evt_ring_mult)
+{
+	u32 channel_id = ipa_client_channel_id(ep->client);
+	u32 tlv_count = ipa_client_tlv_count(ep->client);
+	bool from_ipa = ipa_consumer(ep->client);
+	bool moderation;
+	bool priority;
+	int ret;
+
+	priority = ep->client == IPA_CLIENT_APPS_CMD_PROD;
+	moderation = !ep->sys->tx.no_intr;
+
+	ret = gsi_channel_alloc(ipa_ctx->gsi, channel_id, channel_count,
+				from_ipa, priority, evt_ring_mult, moderation,
+				ep->sys);
+	if (ret)
+		return ret;
+	ep->channel_id = channel_id;
+
+	gsi_channel_scratch_write(ipa_ctx->gsi, ep->channel_id, tlv_count);
+
+	ret = gsi_channel_start(ipa_ctx->gsi, ep->channel_id);
+	if (ret)
+		gsi_channel_free(ipa_ctx->gsi, ep->channel_id);
+
+	return ret;
+}
+
+void ipa_endp_init_hdr_cons(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_cons(&ep->init_hdr, header_size, metadata_offset,
+				   length_offset);
+}
+
+void ipa_endp_init_hdr_prod(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_prod(&ep->init_hdr, header_size, metadata_offset,
+				   length_offset);
+}
+
+void
+ipa_endp_init_hdr_ext_cons(u32 ep_id, u32 pad_align, bool pad_included)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_ext_cons(&ep->hdr_ext, pad_align, pad_included);
+}
+
+void ipa_endp_init_hdr_ext_prod(u32 ep_id, u32 pad_align)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_ext_prod(&ep->hdr_ext, pad_align);
+}
+
+void
+ipa_endp_init_aggr_cons(u32 ep_id, u32 size, u32 count, bool close_on_eof)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_aggr_cons(&ep->init_aggr, size, count, close_on_eof);
+}
+
+void ipa_endp_init_aggr_prod(u32 ep_id, enum ipa_aggr_en aggr_en,
+			     enum ipa_aggr_type aggr_type)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_aggr_prod(&ep->init_aggr, aggr_en, aggr_type);
+}
+
+void ipa_endp_init_cfg_cons(u32 ep_id, enum ipa_cs_offload_en offload_type)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_cfg_cons(&ep->init_cfg, offload_type);
+}
+
+void ipa_endp_init_cfg_prod(u32 ep_id, enum ipa_cs_offload_en offload_type,
+			    u32 metadata_offset)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_cfg_prod(&ep->init_cfg, offload_type,
+				   metadata_offset);
+}
+
+void ipa_endp_init_hdr_metadata_mask_cons(u32 ep_id, u32 mask)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_metadata_mask_cons(&ep->metadata_mask, mask);
+}
+
+void ipa_endp_init_hdr_metadata_mask_prod(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_hdr_metadata_mask_prod(&ep->metadata_mask);
+}
+
+void ipa_endp_status_cons(u32 ep_id, bool enable)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_status_cons(&ep->status, enable);
+}
+
+void ipa_endp_status_prod(u32 ep_id, bool enable,
+			  enum ipa_client_type status_client)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 status_ep_id;
+
+	status_ep_id = ipa_client_ep_id(status_client);
+
+	ipa_reg_endp_status_prod(&ep->status, enable, status_ep_id);
+}
+
+
+/* Note that the mode setting is not valid for consumer endpoints */
+void ipa_endp_init_mode_cons(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_mode_cons(&ep->init_mode);
+}
+
+void ipa_endp_init_mode_prod(u32 ep_id, enum ipa_mode mode,
+			     enum ipa_client_type dst_client)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 dst_ep_id;
+
+	dst_ep_id = ipa_client_ep_id(dst_client);
+
+	ipa_reg_endp_init_mode_prod(&ep->init_mode, mode, dst_ep_id);
+}
+
+/* XXX The sequencer setting seems not to be valid for consumer endpoints */
+void ipa_endp_init_seq_cons(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_seq_cons(&ep->init_seq);
+}
+
+void ipa_endp_init_seq_prod(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 seq_type;
+
+	seq_type = (u32)ipa_endp_seq_type(ep_id);
+
+	ipa_reg_endp_init_seq_prod(&ep->init_seq, seq_type);
+}
+
+/* XXX The deaggr setting seems not to be valid for consumer endpoints */
+void ipa_endp_init_deaggr_cons(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_deaggr_cons(&ep->init_deaggr);
+}
+
+void ipa_endp_init_deaggr_prod(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_reg_endp_init_deaggr_prod(&ep->init_deaggr);
+}
+
+int ipa_ep_alloc(enum ipa_client_type client)
+{
+	u32 ep_id = ipa_client_ep_id(client);
+	struct ipa_sys_context *sys;
+	struct ipa_ep_context *ep;
+
+	ep = &ipa_ctx->ep[ep_id];
+
+	ipa_assert(!ep->allocated);
+
+	/* Reuse the endpoint's sys pointer if it is initialized */
+	sys = ep->sys;
+	if (!sys) {
+		sys = ipa_ep_sys_create(client);
+		if (!sys)
+			return -ENOMEM;
+		sys->ep = ep;
+	}
+
+	/* Zero the "mutable" part of the system context */
+	memset(sys, 0, offsetof(struct ipa_sys_context, ep));
+
+	/* Initialize the endpoint context */
+	memset(ep, 0, sizeof(*ep));
+	ep->sys = sys;
+	ep->client = client;
+	ep->allocated = true;
+
+	return ep_id;
+}
+
+void ipa_ep_free(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+
+	ipa_assert(ep->allocated);
+
+	ep->allocated = false;
+}
+
+/**
+ * ipa_ep_setup() - Set up an IPA endpoint
+ * @ep_id:		Endpoint to set up
+ * @channel_count:	Number of transfer elements in the channel
+ * @evt_ring_mult:	Used to determine number of elements in event ring
+ * @rx_buffer_size:	Receive buffer size to use (or 0 for TX endpoitns)
+ * @client_notify:	Notify function to call on completion
+ * @priv:		Value supplied to the notify function
+ *
+ * Returns:	0 if successful, or a negative error code
+ */
+int ipa_ep_setup(u32 ep_id, u32 channel_count, u32 evt_ring_mult,
+		 u32 rx_buffer_size,
+		 void (*client_notify)(void *priv, enum ipa_dp_evt_type type,
+				       unsigned long data),
+		 void *priv)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	int ret;
+
+	if (ipa_consumer(ep->client)) {
+		atomic_set(&ep->sys->rx.curr_polling_state, 0);
+		INIT_DELAYED_WORK(&ep->sys->rx.switch_to_intr_work,
+				  ipa_switch_to_intr_rx_work_func);
+		if (ep->client == IPA_CLIENT_APPS_LAN_CONS)
+			ep->sys->rx.pyld_hdlr = ipa_lan_rx_pyld_hdlr;
+		else
+			ep->sys->rx.pyld_hdlr = ipa_wan_rx_pyld_hdlr;
+		ep->sys->rx.buff_sz = rx_buffer_size;
+		ep->sys->rx.pool_sz = IPA_GENERIC_RX_POOL_SZ;
+		INIT_WORK(&ep->sys->rx.work, ipa_wq_handle_rx);
+		INIT_DELAYED_WORK(&ep->sys->rx.replenish_work,
+				  ipa_replenish_rx_work_func);
+	}
+
+	ep->client_notify = client_notify;
+	ep->priv = priv;
+	ep->napi_enabled = ep->client == IPA_CLIENT_APPS_WAN_CONS;
+
+	ipa_client_add();
+
+	ipa_cfg_ep(ep_id);
+
+	ret = ipa_gsi_setup_channel(ep, channel_count, evt_ring_mult);
+	if (ret)
+		goto err_client_remove;
+
+	if (ipa_consumer(ep->client))
+		ipa_replenish_rx_cache(ep->sys);
+err_client_remove:
+	ipa_client_remove();
+
+	return ret;
+}
+
+/**
+ * ipa_channel_reset_aggr() - Reset with aggregation active
+ * @ep_id:	Endpoint on which reset is performed
+ *
+ * If aggregation is active on a channel when a reset is performed,
+ * a special sequence of actions must be taken.  This is a workaround
+ * for a hardware limitation.
+ *
+ * Return:	0 if successful, or a negative error code.
+ */
+static int ipa_channel_reset_aggr(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	struct ipa_reg_aggr_force_close force_close;
+	struct ipa_reg_endp_init_ctrl init_ctrl;
+	struct gsi_xfer_elem xfer_elem = { };
+	struct ipa_dma_mem dma_byte;
+	int aggr_active_bitmap = 0;
+	bool ep_suspended = false;
+	int ret;
+	int i;
+
+	ipa_reg_aggr_force_close(&force_close, BIT(ep_id));
+	ipa_write_reg_fields(IPA_AGGR_FORCE_CLOSE, &force_close);
+
+	/* Reset channel */
+	ret = gsi_channel_reset(ipa_ctx->gsi, ep->channel_id);
+	if (ret)
+		return ret;
+
+	/* Turn off the doorbell engine.  We're going to poll until
+	 * we know aggregation isn't active.
+	 */
+	gsi_channel_config(ipa_ctx->gsi, ep->channel_id, false);
+
+	ipa_read_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+	if (init_ctrl.endp_suspend) {
+		ep_suspended = true;
+		ipa_reg_endp_init_ctrl(&init_ctrl, false);
+		ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+	}
+
+	/* Start channel and put 1 Byte descriptor on it */
+	ret = gsi_channel_start(ipa_ctx->gsi, ep->channel_id);
+	if (ret)
+		goto out_suspend_again;
+
+	if (ipa_dma_alloc(&dma_byte, 1, GFP_KERNEL)) {
+		ret = -ENOMEM;
+		goto err_stop_channel;
+	}
+
+	xfer_elem.addr = dma_byte.phys;
+	xfer_elem.len_opcode = 1;	/* = dma_byte.size; */
+	xfer_elem.flags = GSI_XFER_FLAG_EOT;
+	xfer_elem.type = GSI_XFER_ELEM_DATA;
+
+	ret = gsi_channel_queue(ipa_ctx->gsi, ep->channel_id, 1, &xfer_elem,
+				true);
+	if (ret)
+		goto err_dma_free;
+
+	/* Wait for aggregation frame to be closed */
+	for (i = 0; i < CHANNEL_RESET_AGGR_RETRY_COUNT; i++) {
+		aggr_active_bitmap = ipa_read_reg(IPA_STATE_AGGR_ACTIVE);
+		if (!(aggr_active_bitmap & BIT(ep_id)))
+			break;
+		msleep(CHANNEL_RESET_DELAY);
+	}
+	ipa_bug_on(aggr_active_bitmap & BIT(ep_id));
+
+	ipa_dma_free(&dma_byte);
+
+	ret = ipa_stop_gsi_channel(ep_id);
+	if (ret)
+		goto out_suspend_again;
+
+	/* Reset the channel.  If successful we need to sleep for 1
+	 * msec to complete the GSI channel reset sequence.  Either
+	 * way we finish by suspending the channel again (if necessary)
+	 * and re-enabling its doorbell engine.
+	 */
+	ret = gsi_channel_reset(ipa_ctx->gsi, ep->channel_id);
+	if (!ret)
+		msleep(CHANNEL_RESET_DELAY);
+	goto out_suspend_again;
+
+err_dma_free:
+	ipa_dma_free(&dma_byte);
+err_stop_channel:
+	ipa_stop_gsi_channel(ep_id);
+out_suspend_again:
+	if (ep_suspended) {
+		ipa_reg_endp_init_ctrl(&init_ctrl, true);
+		ipa_write_reg_n_fields(IPA_ENDP_INIT_CTRL_N, ep_id, &init_ctrl);
+	}
+	/* Turn on the doorbell engine again */
+	gsi_channel_config(ipa_ctx->gsi, ep->channel_id, true);
+
+	return ret;
+}
+
+static void ipa_reset_gsi_channel(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	u32 aggr_active_bitmap = 0;
+
+	/* For consumer endpoints, a hardware limitation prevents us
+	 * from issuing a channel reset if aggregation is active.
+	 * Check for this case, and if detected, perform a special
+	 * reset sequence.  Otherwise just do a "normal" reset.
+	 */
+	if (ipa_consumer(ep->client))
+		aggr_active_bitmap = ipa_read_reg(IPA_STATE_AGGR_ACTIVE);
+
+	if (aggr_active_bitmap & BIT(ep_id)) {
+		ipa_bug_on(ipa_channel_reset_aggr(ep_id));
+	} else {
+		/* In case the reset follows stop, need to wait 1 msec */
+		msleep(CHANNEL_RESET_DELAY);
+		ipa_bug_on(gsi_channel_reset(ipa_ctx->gsi, ep->channel_id));
+	}
+}
+
+/**
+ * ipa_ep_teardown() - Tear down an endpoint
+ * @ep_id:	The endpoint to tear down
+ */
+void ipa_ep_teardown(u32 ep_id)
+{
+	struct ipa_ep_context *ep = &ipa_ctx->ep[ep_id];
+	int empty;
+	int ret;
+	int i;
+
+	if (ep->napi_enabled) {
+		do {
+			usleep_range(95, 105);
+		} while (ipa_ep_polling(ep));
+	}
+
+	if (ipa_producer(ep->client)) {
+		do {
+			spin_lock_bh(&ep->sys->spinlock);
+			empty = list_empty(&ep->sys->head_desc_list);
+			spin_unlock_bh(&ep->sys->spinlock);
+			if (!empty)
+				usleep_range(95, 105);
+			else
+				break;
+		} while (1);
+	}
+
+	if (ipa_consumer(ep->client))
+		cancel_delayed_work_sync(&ep->sys->rx.replenish_work);
+	flush_workqueue(ep->sys->wq);
+	/* channel stop might fail on timeout if IPA is busy */
+	for (i = 0; i < IPA_GSI_CHANNEL_STOP_MAX_RETRY; i++) {
+		ret = ipa_stop_gsi_channel(ep_id);
+		if (!ret)
+			break;
+		ipa_bug_on(ret != -EAGAIN && ret != -ETIMEDOUT);
+	}
+
+	ipa_reset_gsi_channel(ep_id);
+	gsi_channel_free(ipa_ctx->gsi, ep->channel_id);
+
+	if (ipa_consumer(ep->client))
+		ipa_cleanup_rx(ep->sys);
+
+	ipa_ep_free(ep_id);
+}
+
+static int ipa_poll_gsi_pkt(struct ipa_sys_context *sys)
+{
+	if (sys->ep->bytes_xfered_valid) {
+		sys->ep->bytes_xfered_valid = false;
+
+		return (int)sys->ep->bytes_xfered;
+	}
+
+	return gsi_channel_poll(ipa_ctx->gsi, sys->ep->channel_id);
+}
+
+bool ipa_ep_polling(struct ipa_ep_context *ep)
+{
+	ipa_assert(ipa_consumer(ep->client));
+
+	return !!atomic_read(&ep->sys->rx.curr_polling_state);
+}
+
+struct ipa_dp *ipa_dp_init(void)
+{
+	struct kmem_cache *cache;
+	struct ipa_dp *dp;
+
+	dp = kzalloc(sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return NULL;
+
+	cache = kmem_cache_create("IPA_TX_PKT_WRAPPER",
+				  sizeof(struct ipa_tx_pkt_wrapper),
+				  0, 0, NULL);
+	if (!cache) {
+		kfree(dp);
+		return NULL;
+	}
+	dp->tx_pkt_wrapper_cache = cache;
+
+	cache = kmem_cache_create("IPA_RX_PKT_WRAPPER",
+				  sizeof(struct ipa_rx_pkt_wrapper),
+				  0, 0, NULL);
+	if (!cache) {
+		kmem_cache_destroy(dp->tx_pkt_wrapper_cache);
+		kfree(dp);
+		return NULL;
+	}
+	dp->rx_pkt_wrapper_cache = cache;
+
+	return dp;
+}
+
+void ipa_dp_exit(struct ipa_dp *dp)
+{
+	kmem_cache_destroy(dp->rx_pkt_wrapper_cache);
+	kmem_cache_destroy(dp->tx_pkt_wrapper_cache);
+	kfree(dp);
+}
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

The IPA uses "rmnet" as a way to present remote network resources as
if they were local to the AP.  IPA interfaces representing networks
accessible via the modem are represented as rmnet interfaces,
implemented by the "rmnet data driver" found here:
    drivers/net/ethernet/qualcomm/rmnet/

The IPA is able to perform aggregation of packets, as well as
checksum offload.  These options (plus others, such as configuring
MTU size) are configurable using an ioctl interface.  In addition,
rmnet devices support multiplexing.

TX packets are handed to the data path layer, and when their
transmission is complete the notification callback will be
called.  The data path code posts RX packets to the hardware,
and when they are filled they are supplied here by a receive
notification callback.

The IPA driver currently does not support the modem shutting down
(or crashing).  But the rmnet_ipa device roughly represents the
availability of networks reachable by the modem.  If the modem is
operational, an ipa_rmnet network device will be available.  Modem
operation is managed by the remoteproc subsystem.

Note:  This portion of the driver will be heavily affected by
planned rework on the data path code.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/msm_rmnet.h    | 120 +++++
 drivers/net/ipa/rmnet_config.h |  31 ++
 drivers/net/ipa/rmnet_ipa.c    | 805 +++++++++++++++++++++++++++++++++
 3 files changed, 956 insertions(+)
 create mode 100644 drivers/net/ipa/msm_rmnet.h
 create mode 100644 drivers/net/ipa/rmnet_config.h
 create mode 100644 drivers/net/ipa/rmnet_ipa.c

diff --git a/drivers/net/ipa/msm_rmnet.h b/drivers/net/ipa/msm_rmnet.h
new file mode 100644
index 000000000000..042380fd53fb
--- /dev/null
+++ b/drivers/net/ipa/msm_rmnet.h
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _MSM_RMNET_H_
+#define _MSM_RMNET_H_
+
+/* Bitmap macros for RmNET driver operation mode. */
+#define RMNET_MODE_NONE	    0x00
+#define RMNET_MODE_LLP_ETH  0x01
+#define RMNET_MODE_LLP_IP   0x02
+#define RMNET_MODE_QOS	    0x04
+
+/* IOCTL commands
+ * Values chosen to not conflict with other drivers in the ecosystem
+ */
+
+#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet protocol  */
+#define RMNET_IOCTL_SET_LLP_IP	     0x000089f2 /* Set RAWIP protocol	  */
+#define RMNET_IOCTL_GET_LLP	     0x000089f3 /* Get link protocol	  */
+#define RMNET_IOCTL_SET_QOS_ENABLE   0x000089f4 /* Set QoS header enabled */
+#define RMNET_IOCTL_SET_QOS_DISABLE  0x000089f5 /* Set QoS header disabled*/
+#define RMNET_IOCTL_GET_QOS	     0x000089f6 /* Get QoS header state	  */
+#define RMNET_IOCTL_GET_OPMODE	     0x000089f7 /* Get operation mode	  */
+#define RMNET_IOCTL_OPEN	     0x000089f8 /* Open transport port	  */
+#define RMNET_IOCTL_CLOSE	     0x000089f9 /* Close transport port	  */
+#define RMNET_IOCTL_FLOW_ENABLE	     0x000089fa /* Flow enable		  */
+#define RMNET_IOCTL_FLOW_DISABLE     0x000089fb /* Flow disable		  */
+#define RMNET_IOCTL_FLOW_SET_HNDL    0x000089fc /* Set flow handle	  */
+#define RMNET_IOCTL_EXTENDED	     0x000089fd /* Extended IOCTLs	  */
+
+/* RmNet Data Required IOCTLs */
+#define RMNET_IOCTL_GET_SUPPORTED_FEATURES     0x0000	/* Get features	   */
+#define RMNET_IOCTL_SET_MRU		       0x0001	/* Set MRU	   */
+#define RMNET_IOCTL_GET_MRU		       0x0002	/* Get MRU	   */
+#define RMNET_IOCTL_GET_EPID		       0x0003	/* Get endpoint ID */
+#define RMNET_IOCTL_GET_DRIVER_NAME	       0x0004	/* Get driver name */
+#define RMNET_IOCTL_ADD_MUX_CHANNEL	       0x0005	/* Add MUX ID	   */
+#define RMNET_IOCTL_SET_EGRESS_DATA_FORMAT     0x0006	/* Set EDF	   */
+#define RMNET_IOCTL_SET_INGRESS_DATA_FORMAT    0x0007	/* Set IDF	   */
+#define RMNET_IOCTL_SET_AGGREGATION_COUNT      0x0008	/* Set agg count   */
+#define RMNET_IOCTL_GET_AGGREGATION_COUNT      0x0009	/* Get agg count   */
+#define RMNET_IOCTL_SET_AGGREGATION_SIZE       0x000a	/* Set agg size	   */
+#define RMNET_IOCTL_GET_AGGREGATION_SIZE       0x000b	/* Get agg size	   */
+#define RMNET_IOCTL_FLOW_CONTROL	       0x000c	/* Do flow control */
+#define RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL   0x000d	/* For legacy use  */
+#define RMNET_IOCTL_GET_HWSW_MAP	       0x000e	/* Get HW/SW map   */
+#define RMNET_IOCTL_SET_RX_HEADROOM	       0x000f	/* RX Headroom	   */
+#define RMNET_IOCTL_GET_EP_PAIR		       0x0010	/* Endpoint pair   */
+#define RMNET_IOCTL_SET_QOS_VERSION	       0x0011	/* 8/6 byte QoS hdr*/
+#define RMNET_IOCTL_GET_QOS_VERSION	       0x0012	/* 8/6 byte QoS hdr*/
+#define RMNET_IOCTL_GET_SUPPORTED_QOS_MODES    0x0013	/* Get QoS modes   */
+#define RMNET_IOCTL_SET_SLEEP_STATE	       0x0014	/* Set sleep state */
+#define RMNET_IOCTL_SET_XLAT_DEV_INFO	       0x0015	/* xlat dev name   */
+#define RMNET_IOCTL_DEREGISTER_DEV	       0x0016	/* Dereg a net dev */
+#define RMNET_IOCTL_GET_SG_SUPPORT	       0x0017	/* Query sg support*/
+
+/* Return values for the RMNET_IOCTL_GET_SUPPORTED_FEATURES IOCTL */
+#define RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL		BIT(0)
+#define RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT		BIT(1)
+#define RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT	BIT(2)
+
+/* Input values for the RMNET_IOCTL_SET_EGRESS_DATA_FORMAT IOCTL  */
+#define RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION		BIT(2)
+#define RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM		BIT(4)
+
+/* Input values for the RMNET_IOCTL_SET_INGRESS_DATA_FORMAT IOCTL */
+#define RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM		BIT(4)
+#define RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA		BIT(5)
+
+/* User space may not have this defined. */
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+struct rmnet_ioctl_extended_s {
+	u32	extended_ioctl;
+	union {
+		u32	data; /* Generic data field for most extended IOCTLs */
+
+		/* Return values for
+		 *    RMNET_IOCTL_GET_DRIVER_NAME
+		 *    RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL
+		 */
+		char	if_name[IFNAMSIZ];
+
+		/* Input values for the RMNET_IOCTL_ADD_MUX_CHANNEL IOCTL */
+		struct {
+			u32	mux_id;
+			char	vchannel_name[IFNAMSIZ];
+		} rmnet_mux_val;
+
+		/* Input values for the RMNET_IOCTL_FLOW_CONTROL IOCTL */
+		struct {
+			u8	flow_mode;
+			u8	mux_id;
+		} flow_control_prop;
+
+		/* Return values for RMNET_IOCTL_GET_EP_PAIR */
+		struct {
+			u32	consumer_pipe_num;
+			u32	producer_pipe_num;
+		} ipa_ep_pair;
+
+		struct {
+			u32	__data; /* Placeholder for legacy data*/
+			u32	agg_size;
+			u32	agg_count;
+		} ingress_format;
+	} u;
+};
+
+struct rmnet_ioctl_data_s {
+	union {
+		u32	operation_mode;
+		u32	tcm_handle;
+	} u;
+};
+#endif /* _MSM_RMNET_H_ */
diff --git a/drivers/net/ipa/rmnet_config.h b/drivers/net/ipa/rmnet_config.h
new file mode 100644
index 000000000000..3b9a549ca1bd
--- /dev/null
+++ b/drivers/net/ipa/rmnet_config.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _RMNET_CONFIG_H_
+#define _RMNET_CONFIG_H_
+
+#include <linux/types.h>
+
+/* XXX We want to use struct rmnet_map_header, but that's currently defined in
+ * XXX     drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
+ * XXX We also want to use RMNET_MAP_GET_CD_BIT(Y), defined in the same file.
+ */
+struct rmnet_map_header_s {
+#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
+	u8	pad_len		: 6,
+		reserved_bit	: 1,
+		cd_bit		: 1;
+#else
+	u8	cd_bit		: 1,
+		reserved_bit	: 1,
+		pad_len		: 6;
+#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
+	u8	mux_id;
+	u16	pkt_len;
+}  __aligned(1);
+
+#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *)Y->data)->cd_bit)
+
+#endif /* _RMNET_CONFIG_H_ */
diff --git a/drivers/net/ipa/rmnet_ipa.c b/drivers/net/ipa/rmnet_ipa.c
new file mode 100644
index 000000000000..7006afe3a5ea
--- /dev/null
+++ b/drivers/net/ipa/rmnet_ipa.c
@@ -0,0 +1,805 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+/* WWAN Transport Network Driver. */
+
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_device.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/version.h>
+#include <linux/workqueue.h>
+#include <net/pkt_sched.h>
+
+#include "msm_rmnet.h"
+#include "rmnet_config.h"
+#include "ipa_qmi.h"
+#include "ipa_i.h"
+
+#define DRIVER_NAME		"wwan_ioctl"
+#define IPA_WWAN_DEV_NAME	"rmnet_ipa%d"
+
+#define MUX_CHANNEL_MAX		10	/* max mux channels */
+
+#define NAPI_WEIGHT		60
+
+#define WWAN_DATA_LEN		2000
+#define HEADROOM_FOR_QMAP	8	/* for mux header */
+#define TAILROOM		0	/* for padding by mux layer */
+
+#define DEFAULT_OUTSTANDING_HIGH	128
+#define DEFAULT_OUTSTANDING_HIGH_CTL	(DEFAULT_OUTSTANDING_HIGH + 32)
+#define DEFAULT_OUTSTANDING_LOW		64
+
+#define IPA_APPS_WWAN_CONS_RING_COUNT	256
+#define IPA_APPS_WWAN_PROD_RING_COUNT	512
+
+static int ipa_rmnet_poll(struct napi_struct *napi, int budget);
+
+/** struct ipa_wwan_private - WWAN private data
+ * @net: network interface struct implemented by this driver
+ * @stats: iface statistics
+ * @outstanding_high: number of outstanding packets allowed
+ * @outstanding_low: number of outstanding packets which shall cause
+ *
+ * WWAN private - holds all relevant info about WWAN driver
+ */
+struct ipa_wwan_private {
+	struct net_device_stats stats;
+	atomic_t outstanding_pkts;
+	int outstanding_high_ctl;
+	int outstanding_high;
+	int outstanding_low;
+	struct napi_struct napi;
+};
+
+struct rmnet_ipa_context {
+	struct net_device *dev;
+	struct mutex mux_id_mutex;		/* protects mux_id[] */
+	u32 mux_id_count;
+	u32 mux_id[MUX_CHANNEL_MAX];
+	u32 wan_prod_ep_id;
+	u32 wan_cons_ep_id;
+	struct mutex ep_setup_mutex;		/* endpoint setup/teardown */
+};
+
+static bool initialized;	/* Avoid duplicate initialization */
+
+static struct rmnet_ipa_context rmnet_ipa_ctx_struct;
+static struct rmnet_ipa_context *rmnet_ipa_ctx = &rmnet_ipa_ctx_struct;
+
+/** wwan_open() - Opens the wwan network interface */
+static int ipa_wwan_open(struct net_device *dev)
+{
+	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
+
+	napi_enable(&wwan_ptr->napi);
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+/** ipa_wwan_stop() - Stops the wwan network interface. */
+static int ipa_wwan_stop(struct net_device *dev)
+{
+	netif_stop_queue(dev);
+
+	return 0;
+}
+
+/** ipa_wwan_xmit() - Transmits an skb.
+ *
+ * @skb: skb to be transmitted
+ * @dev: network device
+ *
+ * Return codes:
+ * NETDEV_TX_OK: Success
+ * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later
+ */
+static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
+	unsigned int skb_len;
+	int outstanding;
+
+	if (skb->protocol != htons(ETH_P_MAP)) {
+		dev_kfree_skb_any(skb);
+		dev->stats.tx_dropped++;
+		return NETDEV_TX_OK;
+	}
+
+	/* Control packets are sent even if queue is stopped.  We
+	 * always honor the data and control high-water marks.
+	 */
+	outstanding = atomic_read(&wwan_ptr->outstanding_pkts);
+	if (!RMNET_MAP_GET_CD_BIT(skb)) {	/* Data packet? */
+		if (netif_queue_stopped(dev))
+			return NETDEV_TX_BUSY;
+		if (outstanding >= wwan_ptr->outstanding_high)
+			return NETDEV_TX_BUSY;
+	} else if (outstanding >= wwan_ptr->outstanding_high_ctl) {
+		return NETDEV_TX_BUSY;
+	}
+
+	/* both data packets and commands will be routed to
+	 * IPA_CLIENT_Q6_WAN_CONS based on status configuration.
+	 */
+	skb_len = skb->len;
+	if (ipa_tx_dp(IPA_CLIENT_APPS_WAN_PROD, skb))
+		return NETDEV_TX_BUSY;
+
+	atomic_inc(&wwan_ptr->outstanding_pkts);
+	dev->stats.tx_packets++;
+	dev->stats.tx_bytes += skb_len;
+
+	return NETDEV_TX_OK;
+}
+
+/** apps_ipa_tx_complete_notify() - Rx notify
+ *
+ * @priv: driver context
+ * @evt: event type
+ * @data: data provided with event
+ *
+ * Check that the packet is the one we sent and release it
+ * This function will be called in defered context in IPA wq.
+ */
+static void apps_ipa_tx_complete_notify(void *priv, enum ipa_dp_evt_type evt,
+					unsigned long data)
+{
+	struct ipa_wwan_private *wwan_ptr;
+	struct net_device *dev = priv;
+	struct sk_buff *skb;
+
+	skb = (struct sk_buff *)data;
+
+	if (dev != rmnet_ipa_ctx->dev) {
+		dev_kfree_skb_any(skb);
+		return;
+	}
+
+	if (evt != IPA_WRITE_DONE) {
+		ipa_err("unsupported evt on Tx callback, Drop the packet\n");
+		dev_kfree_skb_any(skb);
+		dev->stats.tx_dropped++;
+		return;
+	}
+
+	wwan_ptr = netdev_priv(dev);
+	atomic_dec(&wwan_ptr->outstanding_pkts);
+	__netif_tx_lock_bh(netdev_get_tx_queue(dev, 0));
+	if (netif_queue_stopped(dev) &&
+	    atomic_read(&wwan_ptr->outstanding_pkts) <
+				wwan_ptr->outstanding_low) {
+		netif_wake_queue(dev);
+	}
+
+	__netif_tx_unlock_bh(netdev_get_tx_queue(dev, 0));
+	dev_kfree_skb_any(skb);
+}
+
+/** apps_ipa_packet_receive_notify() - Rx notify
+ *
+ * @priv: driver context
+ * @evt: event type
+ * @data: data provided with event
+ *
+ * IPA will pass a packet to the Linux network stack with skb->data
+ */
+static void apps_ipa_packet_receive_notify(void *priv, enum ipa_dp_evt_type evt,
+					   unsigned long data)
+{
+	struct ipa_wwan_private *wwan_ptr;
+	struct net_device *dev = priv;
+
+	wwan_ptr = netdev_priv(dev);
+	if (evt == IPA_RECEIVE) {
+		struct sk_buff *skb = (struct sk_buff *)data;
+		int ret;
+		unsigned int packet_len = skb->len;
+
+		skb->dev = rmnet_ipa_ctx->dev;
+		skb->protocol = htons(ETH_P_MAP);
+
+		ret = netif_receive_skb(skb);
+		if (ret) {
+			pr_err_ratelimited("fail on netif_receive_skb\n");
+			dev->stats.rx_dropped++;
+		}
+		dev->stats.rx_packets++;
+		dev->stats.rx_bytes += packet_len;
+	} else if (evt == IPA_CLIENT_START_POLL) {
+		napi_schedule(&wwan_ptr->napi);
+	} else if (evt == IPA_CLIENT_COMP_NAPI) {
+		napi_complete(&wwan_ptr->napi);
+	} else {
+		ipa_err("Invalid evt %d received in wan_ipa_receive\n", evt);
+	}
+}
+
+/** handle_ingress_format() - Ingress data format configuration */
+static int handle_ingress_format(struct net_device *dev,
+				 struct rmnet_ioctl_extended_s *in)
+{
+	enum ipa_cs_offload_en offload_type;
+	enum ipa_client_type client;
+	u32 metadata_offset;
+	u32 rx_buffer_size;
+	u32 channel_count;
+	u32 length_offset;
+	u32 header_size;
+	bool aggr_active;
+	u32 aggr_bytes;
+	u32 aggr_count;
+	u32 aggr_size;	/* in KB */
+	u32 ep_id;
+	int ret;
+
+	client = IPA_CLIENT_APPS_WAN_CONS;
+	channel_count = IPA_APPS_WWAN_CONS_RING_COUNT;
+	header_size = sizeof(struct rmnet_map_header_s);
+	metadata_offset = offsetof(struct rmnet_map_header_s, mux_id);
+	length_offset = offsetof(struct rmnet_map_header_s, pkt_len);
+	offload_type = IPA_CS_OFFLOAD_NONE;
+	aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT;
+	aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT;
+	aggr_active = false;
+
+	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM)
+		offload_type = IPA_CS_OFFLOAD_DL;
+
+	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA) {
+		aggr_bytes = in->u.ingress_format.agg_size;
+		aggr_count = in->u.ingress_format.agg_count;
+		aggr_active = true;
+	}
+
+	if (aggr_bytes > ipa_reg_aggr_max_byte_limit())
+		return -EINVAL;
+
+	if (aggr_count > ipa_reg_aggr_max_packet_limit())
+		return -EINVAL;
+
+	/* Compute the buffer size required to handle the requested
+	 * aggregation byte limit.  The aggr_byte_limit value is
+	 * expressed as a number of KB, but we derive that value
+	 * after computing the buffer size to use (in bytes).  The
+	 * buffer must be sufficient to hold one IPA_MTU-sized
+	 * packet *after* the limit is reached.
+	 *
+	 * (Note that the rx_buffer_size value reflects only the
+	 * space for data, not any standard metadata or headers.)
+	 */
+	rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes);
+
+	/* Account for the extra IPA_MTU past the limit in the
+	 * buffer, and convert the result to the KB units the
+	 * aggr_byte_limit uses.
+	 */
+	aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K;
+
+	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		goto out_unlock;
+	ep_id = ret;
+
+	/* Record our endpoint configuration parameters */
+	ipa_endp_init_hdr_cons(ep_id, header_size, metadata_offset,
+			       length_offset);
+	ipa_endp_init_hdr_ext_cons(ep_id, 0, true);
+	ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, true);
+	ipa_endp_init_cfg_cons(ep_id, offload_type);
+	ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0xff000000);
+	ipa_endp_status_cons(ep_id, !aggr_active);
+
+	ipa_ctx->ipa_client_apps_wan_cons_agg_gro = aggr_active;
+
+	ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size,
+			   apps_ipa_packet_receive_notify, dev);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		rmnet_ipa_ctx->wan_cons_ep_id = ep_id;
+out_unlock:
+	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	return ret;
+}
+
+/** handle_egress_format() - Egress data format configuration */
+static int handle_egress_format(struct net_device *dev,
+				struct rmnet_ioctl_extended_s *e)
+{
+	enum ipa_cs_offload_en offload_type;
+	enum ipa_client_type dst_client;
+	enum ipa_client_type client;
+	enum ipa_aggr_type aggr_type;
+	enum ipa_aggr_en aggr_en;
+	u32 channel_count;
+	u32 length_offset;
+	u32 header_align;
+	u32 header_offset;
+	u32 header_size;
+	u32 ep_id;
+	int ret;
+
+	client = IPA_CLIENT_APPS_WAN_PROD;
+	dst_client = IPA_CLIENT_APPS_LAN_CONS;
+	channel_count = IPA_APPS_WWAN_PROD_RING_COUNT;
+	header_size = sizeof(struct rmnet_map_header_s);
+	offload_type = IPA_CS_OFFLOAD_NONE;
+	aggr_en = IPA_BYPASS_AGGR;
+	aggr_type = 0;	/* ignored if BYPASS */
+	header_offset = 0;
+	length_offset = 0;
+	header_align = 0;
+
+	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM) {
+		offload_type = IPA_CS_OFFLOAD_UL;
+		header_offset = sizeof(struct rmnet_map_header_s) / 4;
+		header_size += sizeof(u32);
+	}
+
+	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION) {
+		aggr_en = IPA_ENABLE_DEAGGR;
+		aggr_type = IPA_QCMAP;
+		length_offset = offsetof(struct rmnet_map_header_s, pkt_len);
+		header_align = ilog2(sizeof(u32));
+	}
+
+	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		goto out_unlock;
+	ep_id = ret;
+
+	if (aggr_en == IPA_ENABLE_DEAGGR && !ipa_endp_aggr_support(ep_id)) {
+		ret = -ENOTSUPP;
+		goto out_unlock;
+	}
+
+	/* We really do want 0 metadata offset */
+	ipa_endp_init_hdr_prod(ep_id, header_size, 0, length_offset);
+	ipa_endp_init_hdr_ext_prod(ep_id, header_align);
+	ipa_endp_init_mode_prod(ep_id, IPA_BASIC, dst_client);
+	ipa_endp_init_aggr_prod(ep_id, aggr_en, aggr_type);
+	ipa_endp_init_cfg_prod(ep_id, offload_type, header_offset);
+	ipa_endp_init_seq_prod(ep_id);
+	ipa_endp_init_deaggr_prod(ep_id);
+	/* Enable source notification status for exception packets
+	 * (i.e. QMAP commands) to be routed to modem.
+	 */
+	ipa_endp_status_prod(ep_id, true, IPA_CLIENT_Q6_WAN_CONS);
+
+	/* Use a deferred interrupting no-op to reduce completion interrupts */
+	ipa_no_intr_init(ep_id);
+
+	ret = ipa_ep_setup(ep_id, channel_count, 1, 0,
+			   apps_ipa_tx_complete_notify, dev);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		rmnet_ipa_ctx->wan_prod_ep_id = ep_id;
+
+out_unlock:
+	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	return ret;
+}
+
+/** ipa_wwan_add_mux_channel() - add a mux_id */
+static int ipa_wwan_add_mux_channel(u32 mux_id)
+{
+	int ret;
+	u32 i;
+
+	mutex_lock(&rmnet_ipa_ctx->mux_id_mutex);
+
+	if (rmnet_ipa_ctx->mux_id_count >= MUX_CHANNEL_MAX) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	for (i = 0; i < rmnet_ipa_ctx->mux_id_count; i++)
+		if (mux_id == rmnet_ipa_ctx->mux_id[i])
+			break;
+
+	/* Record the mux_id if it hasn't already been seen */
+	if (i == rmnet_ipa_ctx->mux_id_count)
+		rmnet_ipa_ctx->mux_id[rmnet_ipa_ctx->mux_id_count++] = mux_id;
+	ret = 0;
+out:
+	mutex_unlock(&rmnet_ipa_ctx->mux_id_mutex);
+
+	return ret;
+}
+
+/** ipa_wwan_ioctl_extended() - rmnet extended I/O control */
+static int ipa_wwan_ioctl_extended(struct net_device *dev, void __user *data)
+{
+	struct rmnet_ioctl_extended_s edata = { };
+	size_t size = sizeof(edata);
+
+	if (copy_from_user(&edata, data, size))
+		return -EFAULT;
+
+	switch (edata.extended_ioctl) {
+	case RMNET_IOCTL_GET_SUPPORTED_FEATURES:	/* Get features */
+		edata.u.data = RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL;
+		edata.u.data |= RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT;
+		edata.u.data |= RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT;
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_EPID:			/* Get endpoint ID */
+		edata.u.data = 1;
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_DRIVER_NAME:		/* Get driver name */
+		memcpy(&edata.u.if_name, rmnet_ipa_ctx->dev->name, IFNAMSIZ);
+		goto copy_out;
+
+	case RMNET_IOCTL_ADD_MUX_CHANNEL:		/* Add MUX ID */
+		return ipa_wwan_add_mux_channel(edata.u.rmnet_mux_val.mux_id);
+
+	case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:	/* Egress data format */
+		return handle_egress_format(dev, &edata) ? -EFAULT : 0;
+
+	case RMNET_IOCTL_SET_INGRESS_DATA_FORMAT:	/* Ingress format */
+		return handle_ingress_format(dev, &edata) ? -EFAULT : 0;
+
+	case RMNET_IOCTL_GET_EP_PAIR:			/* Get endpoint pair */
+		edata.u.ipa_ep_pair.consumer_pipe_num =
+				ipa_client_ep_id(IPA_CLIENT_APPS_WAN_PROD);
+		edata.u.ipa_ep_pair.producer_pipe_num =
+				ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS);
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_SG_SUPPORT:		/* Get SG support */
+		edata.u.data = 1;	/* Scatter/gather is always supported */
+		goto copy_out;
+
+	/* Unsupported requests */
+	case RMNET_IOCTL_SET_MRU:			/* Set MRU */
+	case RMNET_IOCTL_GET_MRU:			/* Get MRU */
+	case RMNET_IOCTL_GET_AGGREGATION_COUNT:		/* Get agg count */
+	case RMNET_IOCTL_SET_AGGREGATION_COUNT:		/* Set agg count */
+	case RMNET_IOCTL_GET_AGGREGATION_SIZE:		/* Get agg size */
+	case RMNET_IOCTL_SET_AGGREGATION_SIZE:		/* Set agg size */
+	case RMNET_IOCTL_FLOW_CONTROL:			/* Do flow control */
+	case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:	/* For legacy use */
+	case RMNET_IOCTL_GET_HWSW_MAP:			/* Get HW/SW map */
+	case RMNET_IOCTL_SET_RX_HEADROOM:		/* Set RX Headroom */
+	case RMNET_IOCTL_SET_QOS_VERSION:		/* Set 8/6 byte QoS */
+	case RMNET_IOCTL_GET_QOS_VERSION:		/* Get 8/6 byte QoS */
+	case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:	/* Get QoS modes */
+	case RMNET_IOCTL_SET_SLEEP_STATE:		/* Set sleep state */
+	case RMNET_IOCTL_SET_XLAT_DEV_INFO:		/* xlat dev name */
+	case RMNET_IOCTL_DEREGISTER_DEV:		/* Deregister netdev */
+		return -ENOTSUPP;	/* Defined, but unsupported command */
+
+	default:
+		return -EINVAL;		/* Invalid (unrecognized) command */
+	}
+
+copy_out:
+	return copy_to_user(data, &edata, size) ? -EFAULT : 0;
+}
+
+/** ipa_wwan_ioctl() - I/O control for wwan network driver */
+static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct rmnet_ioctl_data_s ioctl_data = { };
+	void __user *data;
+	size_t size;
+
+	data = ifr->ifr_ifru.ifru_data;
+	size = sizeof(ioctl_data);
+
+	switch (cmd) {
+	/* These features are implied; alternatives are not supported */
+	case RMNET_IOCTL_SET_LLP_IP:		/* RAW IP protocol */
+	case RMNET_IOCTL_SET_QOS_DISABLE:	/* QoS header disabled */
+		return 0;
+
+	/* These features are not supported; use alternatives */
+	case RMNET_IOCTL_SET_LLP_ETHERNET:	/* Ethernet protocol */
+	case RMNET_IOCTL_SET_QOS_ENABLE:	/* QoS header enabled */
+	case RMNET_IOCTL_GET_OPMODE:		/* Get operation mode */
+	case RMNET_IOCTL_FLOW_ENABLE:		/* Flow enable */
+	case RMNET_IOCTL_FLOW_DISABLE:		/* Flow disable */
+	case RMNET_IOCTL_FLOW_SET_HNDL:		/* Set flow handle */
+		return -ENOTSUPP;
+
+	case RMNET_IOCTL_GET_LLP:		/* Get link protocol */
+		ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_QOS:		/* Get QoS header state */
+		ioctl_data.u.operation_mode = RMNET_MODE_NONE;
+		goto copy_out;
+
+	case RMNET_IOCTL_OPEN:			/* Open transport port */
+	case RMNET_IOCTL_CLOSE:			/* Close transport port */
+		return 0;
+
+	case RMNET_IOCTL_EXTENDED:		/* Extended IOCTLs */
+		return ipa_wwan_ioctl_extended(dev, data);
+
+	default:
+		return -EINVAL;
+	}
+
+copy_out:
+	return copy_to_user(data, &ioctl_data, size) ? -EFAULT : 0;
+}
+
+static const struct net_device_ops ipa_wwan_ops_ip = {
+	.ndo_open	= ipa_wwan_open,
+	.ndo_stop	= ipa_wwan_stop,
+	.ndo_start_xmit	= ipa_wwan_xmit,
+	.ndo_do_ioctl	= ipa_wwan_ioctl,
+};
+
+/** wwan_setup() - Setup the wwan network driver */
+static void ipa_wwan_setup(struct net_device *dev)
+{
+	dev->netdev_ops = &ipa_wwan_ops_ip;
+	ether_setup(dev);
+	dev->header_ops = NULL;	 /* No header (override ether_setup() value) */
+	dev->type = ARPHRD_RAWIP;
+	dev->hard_header_len = 0;
+	dev->max_mtu = WWAN_DATA_LEN;
+	dev->mtu = dev->max_mtu;
+	dev->addr_len = 0;
+	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
+	dev->needed_headroom = HEADROOM_FOR_QMAP;
+	dev->needed_tailroom = TAILROOM;
+	dev->watchdog_timeo = msecs_to_jiffies(10 * MSEC_PER_SEC);
+}
+
+/** ipa_wwan_probe() - Network probe function */
+static int ipa_wwan_probe(struct platform_device *pdev)
+{
+	struct ipa_wwan_private *wwan_ptr;
+	struct net_device *dev;
+	int ret;
+
+	mutex_init(&rmnet_ipa_ctx->ep_setup_mutex);
+	mutex_init(&rmnet_ipa_ctx->mux_id_mutex);
+
+	/* Mark client handles bad until we initialize them */
+	rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
+	rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
+
+	ret = ipa_modem_smem_init();
+	if (ret)
+		goto err_clear_ctx;
+
+	/* start A7 QMI service/client */
+	ipa_qmi_init();
+
+	/* initialize wan-driver netdev */
+	dev = alloc_netdev(sizeof(struct ipa_wwan_private),
+			   IPA_WWAN_DEV_NAME,
+			   NET_NAME_UNKNOWN,
+			   ipa_wwan_setup);
+	if (!dev) {
+		ipa_err("no memory for netdev\n");
+		ret = -ENOMEM;
+		goto err_clear_ctx;
+	}
+	rmnet_ipa_ctx->dev = dev;
+	wwan_ptr = netdev_priv(dev);
+	wwan_ptr->outstanding_high_ctl = DEFAULT_OUTSTANDING_HIGH_CTL;
+	wwan_ptr->outstanding_high = DEFAULT_OUTSTANDING_HIGH;
+	wwan_ptr->outstanding_low = DEFAULT_OUTSTANDING_LOW;
+	atomic_set(&wwan_ptr->outstanding_pkts, 0);
+
+	/* Enable SG support in netdevice. */
+	dev->hw_features |= NETIF_F_SG;
+
+	netif_napi_add(dev, &wwan_ptr->napi, ipa_rmnet_poll, NAPI_WEIGHT);
+	ret = register_netdev(dev);
+	if (ret) {
+		ipa_err("unable to register ipa_netdev %d rc=%d\n", 0, ret);
+		goto err_napi_del;
+	}
+
+	/* offline charging mode */
+	ipa_proxy_clk_unvote();
+
+	/* Till the system is suspended, we keep the clock open */
+	ipa_client_add();
+
+	initialized = true;
+
+	return 0;
+
+err_napi_del:
+	netif_napi_del(&wwan_ptr->napi);
+	free_netdev(dev);
+err_clear_ctx:
+	memset(&rmnet_ipa_ctx_struct, 0, sizeof(rmnet_ipa_ctx_struct));
+
+	return ret;
+}
+
+static int ipa_wwan_remove(struct platform_device *pdev)
+{
+	struct ipa_wwan_private *wwan_ptr = netdev_priv(rmnet_ipa_ctx->dev);
+
+	dev_info(&pdev->dev, "rmnet_ipa started deinitialization\n");
+
+	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	ipa_client_add();
+
+	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_teardown(rmnet_ipa_ctx->wan_cons_ep_id);
+		rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
+	}
+
+	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_teardown(rmnet_ipa_ctx->wan_prod_ep_id);
+		rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
+	}
+
+	ipa_client_remove();
+
+	netif_napi_del(&wwan_ptr->napi);
+	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
+	unregister_netdev(rmnet_ipa_ctx->dev);
+
+	if (rmnet_ipa_ctx->dev)
+		free_netdev(rmnet_ipa_ctx->dev);
+	rmnet_ipa_ctx->dev = NULL;
+
+	mutex_destroy(&rmnet_ipa_ctx->mux_id_mutex);
+	mutex_destroy(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	initialized = false;
+
+	dev_info(&pdev->dev, "rmnet_ipa completed deinitialization\n");
+
+	return 0;
+}
+
+/** rmnet_ipa_ap_suspend() - suspend callback for runtime_pm
+ * @dev: pointer to device
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP suspend
+ * operation is invoked, usually by pressing a suspend button.
+ *
+ * Returns -EAGAIN to runtime_pm framework in case there are pending packets
+ * in the Tx queue. This will postpone the suspend operation until all the
+ * pending packets will be transmitted.
+ *
+ * In case there are no packets to send, releases the WWAN0_PROD entity.
+ * As an outcome, the number of IPA active clients should be decremented
+ * until IPA clocks can be gated.
+ */
+static int rmnet_ipa_ap_suspend(struct device *dev)
+{
+	struct net_device *netdev = rmnet_ipa_ctx->dev;
+	struct ipa_wwan_private *wwan_ptr;
+	int ret;
+
+	if (!netdev) {
+		ipa_err("netdev is NULL.\n");
+		ret = 0;
+		goto bail;
+	}
+
+	netif_tx_lock_bh(netdev);
+	wwan_ptr = netdev_priv(netdev);
+	if (!wwan_ptr) {
+		ipa_err("wwan_ptr is NULL.\n");
+		ret = 0;
+		goto unlock_and_bail;
+	}
+
+	/* Do not allow A7 to suspend in case there are outstanding packets */
+	if (atomic_read(&wwan_ptr->outstanding_pkts) != 0) {
+		ret = -EAGAIN;
+		goto unlock_and_bail;
+	}
+
+	/* Make sure that there is no Tx operation ongoing */
+	netif_stop_queue(netdev);
+
+	ret = 0;
+	ipa_client_remove();
+
+unlock_and_bail:
+	netif_tx_unlock_bh(netdev);
+bail:
+
+	return ret;
+}
+
+/** rmnet_ipa_ap_resume() - resume callback for runtime_pm
+ * @dev: pointer to device
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP resume
+ * operation is invoked.
+ *
+ * Enables the network interface queue and returns success to the
+ * runtime_pm framework.
+ */
+static int rmnet_ipa_ap_resume(struct device *dev)
+{
+	struct net_device *netdev = rmnet_ipa_ctx->dev;
+
+	ipa_client_add();
+	if (netdev)
+		netif_wake_queue(netdev);
+
+	return 0;
+}
+
+static const struct of_device_id rmnet_ipa_dt_match[] = {
+	{.compatible = "qcom,rmnet-ipa"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, rmnet_ipa_dt_match);
+
+static const struct dev_pm_ops rmnet_ipa_pm_ops = {
+	.suspend_noirq = rmnet_ipa_ap_suspend,
+	.resume_noirq = rmnet_ipa_ap_resume,
+};
+
+static struct platform_driver rmnet_ipa_driver = {
+	.driver = {
+		.name = "rmnet_ipa",
+		.owner = THIS_MODULE,
+		.pm = &rmnet_ipa_pm_ops,
+		.of_match_table = rmnet_ipa_dt_match,
+	},
+	.probe = ipa_wwan_probe,
+	.remove = ipa_wwan_remove,
+};
+
+int ipa_wwan_init(void)
+{
+	if (initialized)
+		return 0;
+
+	return platform_driver_register(&rmnet_ipa_driver);
+}
+
+void ipa_wwan_cleanup(void)
+{
+	platform_driver_unregister(&rmnet_ipa_driver);
+	memset(&rmnet_ipa_ctx_struct, 0, sizeof(rmnet_ipa_ctx_struct));
+}
+
+static int ipa_rmnet_poll(struct napi_struct *napi, int budget)
+{
+	return ipa_rx_poll(rmnet_ipa_ctx->wan_cons_ep_id, budget);
+}
+
+MODULE_DESCRIPTION("WWAN Network Interface");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

The IPA uses "rmnet" as a way to present remote network resources as
if they were local to the AP.  IPA interfaces representing networks
accessible via the modem are represented as rmnet interfaces,
implemented by the "rmnet data driver" found here:
    drivers/net/ethernet/qualcomm/rmnet/

The IPA is able to perform aggregation of packets, as well as
checksum offload.  These options (plus others, such as configuring
MTU size) are configurable using an ioctl interface.  In addition,
rmnet devices support multiplexing.

TX packets are handed to the data path layer, and when their
transmission is complete the notification callback will be
called.  The data path code posts RX packets to the hardware,
and when they are filled they are supplied here by a receive
notification callback.

The IPA driver currently does not support the modem shutting down
(or crashing).  But the rmnet_ipa device roughly represents the
availability of networks reachable by the modem.  If the modem is
operational, an ipa_rmnet network device will be available.  Modem
operation is managed by the remoteproc subsystem.

Note:  This portion of the driver will be heavily affected by
planned rework on the data path code.

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/msm_rmnet.h    | 120 +++++
 drivers/net/ipa/rmnet_config.h |  31 ++
 drivers/net/ipa/rmnet_ipa.c    | 805 +++++++++++++++++++++++++++++++++
 3 files changed, 956 insertions(+)
 create mode 100644 drivers/net/ipa/msm_rmnet.h
 create mode 100644 drivers/net/ipa/rmnet_config.h
 create mode 100644 drivers/net/ipa/rmnet_ipa.c

diff --git a/drivers/net/ipa/msm_rmnet.h b/drivers/net/ipa/msm_rmnet.h
new file mode 100644
index 000000000000..042380fd53fb
--- /dev/null
+++ b/drivers/net/ipa/msm_rmnet.h
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _MSM_RMNET_H_
+#define _MSM_RMNET_H_
+
+/* Bitmap macros for RmNET driver operation mode. */
+#define RMNET_MODE_NONE	    0x00
+#define RMNET_MODE_LLP_ETH  0x01
+#define RMNET_MODE_LLP_IP   0x02
+#define RMNET_MODE_QOS	    0x04
+
+/* IOCTL commands
+ * Values chosen to not conflict with other drivers in the ecosystem
+ */
+
+#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet protocol  */
+#define RMNET_IOCTL_SET_LLP_IP	     0x000089f2 /* Set RAWIP protocol	  */
+#define RMNET_IOCTL_GET_LLP	     0x000089f3 /* Get link protocol	  */
+#define RMNET_IOCTL_SET_QOS_ENABLE   0x000089f4 /* Set QoS header enabled */
+#define RMNET_IOCTL_SET_QOS_DISABLE  0x000089f5 /* Set QoS header disabled*/
+#define RMNET_IOCTL_GET_QOS	     0x000089f6 /* Get QoS header state	  */
+#define RMNET_IOCTL_GET_OPMODE	     0x000089f7 /* Get operation mode	  */
+#define RMNET_IOCTL_OPEN	     0x000089f8 /* Open transport port	  */
+#define RMNET_IOCTL_CLOSE	     0x000089f9 /* Close transport port	  */
+#define RMNET_IOCTL_FLOW_ENABLE	     0x000089fa /* Flow enable		  */
+#define RMNET_IOCTL_FLOW_DISABLE     0x000089fb /* Flow disable		  */
+#define RMNET_IOCTL_FLOW_SET_HNDL    0x000089fc /* Set flow handle	  */
+#define RMNET_IOCTL_EXTENDED	     0x000089fd /* Extended IOCTLs	  */
+
+/* RmNet Data Required IOCTLs */
+#define RMNET_IOCTL_GET_SUPPORTED_FEATURES     0x0000	/* Get features	   */
+#define RMNET_IOCTL_SET_MRU		       0x0001	/* Set MRU	   */
+#define RMNET_IOCTL_GET_MRU		       0x0002	/* Get MRU	   */
+#define RMNET_IOCTL_GET_EPID		       0x0003	/* Get endpoint ID */
+#define RMNET_IOCTL_GET_DRIVER_NAME	       0x0004	/* Get driver name */
+#define RMNET_IOCTL_ADD_MUX_CHANNEL	       0x0005	/* Add MUX ID	   */
+#define RMNET_IOCTL_SET_EGRESS_DATA_FORMAT     0x0006	/* Set EDF	   */
+#define RMNET_IOCTL_SET_INGRESS_DATA_FORMAT    0x0007	/* Set IDF	   */
+#define RMNET_IOCTL_SET_AGGREGATION_COUNT      0x0008	/* Set agg count   */
+#define RMNET_IOCTL_GET_AGGREGATION_COUNT      0x0009	/* Get agg count   */
+#define RMNET_IOCTL_SET_AGGREGATION_SIZE       0x000a	/* Set agg size	   */
+#define RMNET_IOCTL_GET_AGGREGATION_SIZE       0x000b	/* Get agg size	   */
+#define RMNET_IOCTL_FLOW_CONTROL	       0x000c	/* Do flow control */
+#define RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL   0x000d	/* For legacy use  */
+#define RMNET_IOCTL_GET_HWSW_MAP	       0x000e	/* Get HW/SW map   */
+#define RMNET_IOCTL_SET_RX_HEADROOM	       0x000f	/* RX Headroom	   */
+#define RMNET_IOCTL_GET_EP_PAIR		       0x0010	/* Endpoint pair   */
+#define RMNET_IOCTL_SET_QOS_VERSION	       0x0011	/* 8/6 byte QoS hdr*/
+#define RMNET_IOCTL_GET_QOS_VERSION	       0x0012	/* 8/6 byte QoS hdr*/
+#define RMNET_IOCTL_GET_SUPPORTED_QOS_MODES    0x0013	/* Get QoS modes   */
+#define RMNET_IOCTL_SET_SLEEP_STATE	       0x0014	/* Set sleep state */
+#define RMNET_IOCTL_SET_XLAT_DEV_INFO	       0x0015	/* xlat dev name   */
+#define RMNET_IOCTL_DEREGISTER_DEV	       0x0016	/* Dereg a net dev */
+#define RMNET_IOCTL_GET_SG_SUPPORT	       0x0017	/* Query sg support*/
+
+/* Return values for the RMNET_IOCTL_GET_SUPPORTED_FEATURES IOCTL */
+#define RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL		BIT(0)
+#define RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT		BIT(1)
+#define RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT	BIT(2)
+
+/* Input values for the RMNET_IOCTL_SET_EGRESS_DATA_FORMAT IOCTL  */
+#define RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION		BIT(2)
+#define RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM		BIT(4)
+
+/* Input values for the RMNET_IOCTL_SET_INGRESS_DATA_FORMAT IOCTL */
+#define RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM		BIT(4)
+#define RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA		BIT(5)
+
+/* User space may not have this defined. */
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+struct rmnet_ioctl_extended_s {
+	u32	extended_ioctl;
+	union {
+		u32	data; /* Generic data field for most extended IOCTLs */
+
+		/* Return values for
+		 *    RMNET_IOCTL_GET_DRIVER_NAME
+		 *    RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL
+		 */
+		char	if_name[IFNAMSIZ];
+
+		/* Input values for the RMNET_IOCTL_ADD_MUX_CHANNEL IOCTL */
+		struct {
+			u32	mux_id;
+			char	vchannel_name[IFNAMSIZ];
+		} rmnet_mux_val;
+
+		/* Input values for the RMNET_IOCTL_FLOW_CONTROL IOCTL */
+		struct {
+			u8	flow_mode;
+			u8	mux_id;
+		} flow_control_prop;
+
+		/* Return values for RMNET_IOCTL_GET_EP_PAIR */
+		struct {
+			u32	consumer_pipe_num;
+			u32	producer_pipe_num;
+		} ipa_ep_pair;
+
+		struct {
+			u32	__data; /* Placeholder for legacy data*/
+			u32	agg_size;
+			u32	agg_count;
+		} ingress_format;
+	} u;
+};
+
+struct rmnet_ioctl_data_s {
+	union {
+		u32	operation_mode;
+		u32	tcm_handle;
+	} u;
+};
+#endif /* _MSM_RMNET_H_ */
diff --git a/drivers/net/ipa/rmnet_config.h b/drivers/net/ipa/rmnet_config.h
new file mode 100644
index 000000000000..3b9a549ca1bd
--- /dev/null
+++ b/drivers/net/ipa/rmnet_config.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _RMNET_CONFIG_H_
+#define _RMNET_CONFIG_H_
+
+#include <linux/types.h>
+
+/* XXX We want to use struct rmnet_map_header, but that's currently defined in
+ * XXX     drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
+ * XXX We also want to use RMNET_MAP_GET_CD_BIT(Y), defined in the same file.
+ */
+struct rmnet_map_header_s {
+#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
+	u8	pad_len		: 6,
+		reserved_bit	: 1,
+		cd_bit		: 1;
+#else
+	u8	cd_bit		: 1,
+		reserved_bit	: 1,
+		pad_len		: 6;
+#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
+	u8	mux_id;
+	u16	pkt_len;
+}  __aligned(1);
+
+#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *)Y->data)->cd_bit)
+
+#endif /* _RMNET_CONFIG_H_ */
diff --git a/drivers/net/ipa/rmnet_ipa.c b/drivers/net/ipa/rmnet_ipa.c
new file mode 100644
index 000000000000..7006afe3a5ea
--- /dev/null
+++ b/drivers/net/ipa/rmnet_ipa.c
@@ -0,0 +1,805 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+/* WWAN Transport Network Driver. */
+
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_device.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/version.h>
+#include <linux/workqueue.h>
+#include <net/pkt_sched.h>
+
+#include "msm_rmnet.h"
+#include "rmnet_config.h"
+#include "ipa_qmi.h"
+#include "ipa_i.h"
+
+#define DRIVER_NAME		"wwan_ioctl"
+#define IPA_WWAN_DEV_NAME	"rmnet_ipa%d"
+
+#define MUX_CHANNEL_MAX		10	/* max mux channels */
+
+#define NAPI_WEIGHT		60
+
+#define WWAN_DATA_LEN		2000
+#define HEADROOM_FOR_QMAP	8	/* for mux header */
+#define TAILROOM		0	/* for padding by mux layer */
+
+#define DEFAULT_OUTSTANDING_HIGH	128
+#define DEFAULT_OUTSTANDING_HIGH_CTL	(DEFAULT_OUTSTANDING_HIGH + 32)
+#define DEFAULT_OUTSTANDING_LOW		64
+
+#define IPA_APPS_WWAN_CONS_RING_COUNT	256
+#define IPA_APPS_WWAN_PROD_RING_COUNT	512
+
+static int ipa_rmnet_poll(struct napi_struct *napi, int budget);
+
+/** struct ipa_wwan_private - WWAN private data
+ * @net: network interface struct implemented by this driver
+ * @stats: iface statistics
+ * @outstanding_high: number of outstanding packets allowed
+ * @outstanding_low: number of outstanding packets which shall cause
+ *
+ * WWAN private - holds all relevant info about WWAN driver
+ */
+struct ipa_wwan_private {
+	struct net_device_stats stats;
+	atomic_t outstanding_pkts;
+	int outstanding_high_ctl;
+	int outstanding_high;
+	int outstanding_low;
+	struct napi_struct napi;
+};
+
+struct rmnet_ipa_context {
+	struct net_device *dev;
+	struct mutex mux_id_mutex;		/* protects mux_id[] */
+	u32 mux_id_count;
+	u32 mux_id[MUX_CHANNEL_MAX];
+	u32 wan_prod_ep_id;
+	u32 wan_cons_ep_id;
+	struct mutex ep_setup_mutex;		/* endpoint setup/teardown */
+};
+
+static bool initialized;	/* Avoid duplicate initialization */
+
+static struct rmnet_ipa_context rmnet_ipa_ctx_struct;
+static struct rmnet_ipa_context *rmnet_ipa_ctx = &rmnet_ipa_ctx_struct;
+
+/** wwan_open() - Opens the wwan network interface */
+static int ipa_wwan_open(struct net_device *dev)
+{
+	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
+
+	napi_enable(&wwan_ptr->napi);
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+/** ipa_wwan_stop() - Stops the wwan network interface. */
+static int ipa_wwan_stop(struct net_device *dev)
+{
+	netif_stop_queue(dev);
+
+	return 0;
+}
+
+/** ipa_wwan_xmit() - Transmits an skb.
+ *
+ * @skb: skb to be transmitted
+ * @dev: network device
+ *
+ * Return codes:
+ * NETDEV_TX_OK: Success
+ * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later
+ */
+static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
+	unsigned int skb_len;
+	int outstanding;
+
+	if (skb->protocol != htons(ETH_P_MAP)) {
+		dev_kfree_skb_any(skb);
+		dev->stats.tx_dropped++;
+		return NETDEV_TX_OK;
+	}
+
+	/* Control packets are sent even if queue is stopped.  We
+	 * always honor the data and control high-water marks.
+	 */
+	outstanding = atomic_read(&wwan_ptr->outstanding_pkts);
+	if (!RMNET_MAP_GET_CD_BIT(skb)) {	/* Data packet? */
+		if (netif_queue_stopped(dev))
+			return NETDEV_TX_BUSY;
+		if (outstanding >= wwan_ptr->outstanding_high)
+			return NETDEV_TX_BUSY;
+	} else if (outstanding >= wwan_ptr->outstanding_high_ctl) {
+		return NETDEV_TX_BUSY;
+	}
+
+	/* both data packets and commands will be routed to
+	 * IPA_CLIENT_Q6_WAN_CONS based on status configuration.
+	 */
+	skb_len = skb->len;
+	if (ipa_tx_dp(IPA_CLIENT_APPS_WAN_PROD, skb))
+		return NETDEV_TX_BUSY;
+
+	atomic_inc(&wwan_ptr->outstanding_pkts);
+	dev->stats.tx_packets++;
+	dev->stats.tx_bytes += skb_len;
+
+	return NETDEV_TX_OK;
+}
+
+/** apps_ipa_tx_complete_notify() - Rx notify
+ *
+ * @priv: driver context
+ * @evt: event type
+ * @data: data provided with event
+ *
+ * Check that the packet is the one we sent and release it
+ * This function will be called in defered context in IPA wq.
+ */
+static void apps_ipa_tx_complete_notify(void *priv, enum ipa_dp_evt_type evt,
+					unsigned long data)
+{
+	struct ipa_wwan_private *wwan_ptr;
+	struct net_device *dev = priv;
+	struct sk_buff *skb;
+
+	skb = (struct sk_buff *)data;
+
+	if (dev != rmnet_ipa_ctx->dev) {
+		dev_kfree_skb_any(skb);
+		return;
+	}
+
+	if (evt != IPA_WRITE_DONE) {
+		ipa_err("unsupported evt on Tx callback, Drop the packet\n");
+		dev_kfree_skb_any(skb);
+		dev->stats.tx_dropped++;
+		return;
+	}
+
+	wwan_ptr = netdev_priv(dev);
+	atomic_dec(&wwan_ptr->outstanding_pkts);
+	__netif_tx_lock_bh(netdev_get_tx_queue(dev, 0));
+	if (netif_queue_stopped(dev) &&
+	    atomic_read(&wwan_ptr->outstanding_pkts) <
+				wwan_ptr->outstanding_low) {
+		netif_wake_queue(dev);
+	}
+
+	__netif_tx_unlock_bh(netdev_get_tx_queue(dev, 0));
+	dev_kfree_skb_any(skb);
+}
+
+/** apps_ipa_packet_receive_notify() - Rx notify
+ *
+ * @priv: driver context
+ * @evt: event type
+ * @data: data provided with event
+ *
+ * IPA will pass a packet to the Linux network stack with skb->data
+ */
+static void apps_ipa_packet_receive_notify(void *priv, enum ipa_dp_evt_type evt,
+					   unsigned long data)
+{
+	struct ipa_wwan_private *wwan_ptr;
+	struct net_device *dev = priv;
+
+	wwan_ptr = netdev_priv(dev);
+	if (evt == IPA_RECEIVE) {
+		struct sk_buff *skb = (struct sk_buff *)data;
+		int ret;
+		unsigned int packet_len = skb->len;
+
+		skb->dev = rmnet_ipa_ctx->dev;
+		skb->protocol = htons(ETH_P_MAP);
+
+		ret = netif_receive_skb(skb);
+		if (ret) {
+			pr_err_ratelimited("fail on netif_receive_skb\n");
+			dev->stats.rx_dropped++;
+		}
+		dev->stats.rx_packets++;
+		dev->stats.rx_bytes += packet_len;
+	} else if (evt == IPA_CLIENT_START_POLL) {
+		napi_schedule(&wwan_ptr->napi);
+	} else if (evt == IPA_CLIENT_COMP_NAPI) {
+		napi_complete(&wwan_ptr->napi);
+	} else {
+		ipa_err("Invalid evt %d received in wan_ipa_receive\n", evt);
+	}
+}
+
+/** handle_ingress_format() - Ingress data format configuration */
+static int handle_ingress_format(struct net_device *dev,
+				 struct rmnet_ioctl_extended_s *in)
+{
+	enum ipa_cs_offload_en offload_type;
+	enum ipa_client_type client;
+	u32 metadata_offset;
+	u32 rx_buffer_size;
+	u32 channel_count;
+	u32 length_offset;
+	u32 header_size;
+	bool aggr_active;
+	u32 aggr_bytes;
+	u32 aggr_count;
+	u32 aggr_size;	/* in KB */
+	u32 ep_id;
+	int ret;
+
+	client = IPA_CLIENT_APPS_WAN_CONS;
+	channel_count = IPA_APPS_WWAN_CONS_RING_COUNT;
+	header_size = sizeof(struct rmnet_map_header_s);
+	metadata_offset = offsetof(struct rmnet_map_header_s, mux_id);
+	length_offset = offsetof(struct rmnet_map_header_s, pkt_len);
+	offload_type = IPA_CS_OFFLOAD_NONE;
+	aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT;
+	aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT;
+	aggr_active = false;
+
+	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM)
+		offload_type = IPA_CS_OFFLOAD_DL;
+
+	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA) {
+		aggr_bytes = in->u.ingress_format.agg_size;
+		aggr_count = in->u.ingress_format.agg_count;
+		aggr_active = true;
+	}
+
+	if (aggr_bytes > ipa_reg_aggr_max_byte_limit())
+		return -EINVAL;
+
+	if (aggr_count > ipa_reg_aggr_max_packet_limit())
+		return -EINVAL;
+
+	/* Compute the buffer size required to handle the requested
+	 * aggregation byte limit.  The aggr_byte_limit value is
+	 * expressed as a number of KB, but we derive that value
+	 * after computing the buffer size to use (in bytes).  The
+	 * buffer must be sufficient to hold one IPA_MTU-sized
+	 * packet *after* the limit is reached.
+	 *
+	 * (Note that the rx_buffer_size value reflects only the
+	 * space for data, not any standard metadata or headers.)
+	 */
+	rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes);
+
+	/* Account for the extra IPA_MTU past the limit in the
+	 * buffer, and convert the result to the KB units the
+	 * aggr_byte_limit uses.
+	 */
+	aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K;
+
+	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		goto out_unlock;
+	ep_id = ret;
+
+	/* Record our endpoint configuration parameters */
+	ipa_endp_init_hdr_cons(ep_id, header_size, metadata_offset,
+			       length_offset);
+	ipa_endp_init_hdr_ext_cons(ep_id, 0, true);
+	ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, true);
+	ipa_endp_init_cfg_cons(ep_id, offload_type);
+	ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0xff000000);
+	ipa_endp_status_cons(ep_id, !aggr_active);
+
+	ipa_ctx->ipa_client_apps_wan_cons_agg_gro = aggr_active;
+
+	ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size,
+			   apps_ipa_packet_receive_notify, dev);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		rmnet_ipa_ctx->wan_cons_ep_id = ep_id;
+out_unlock:
+	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	return ret;
+}
+
+/** handle_egress_format() - Egress data format configuration */
+static int handle_egress_format(struct net_device *dev,
+				struct rmnet_ioctl_extended_s *e)
+{
+	enum ipa_cs_offload_en offload_type;
+	enum ipa_client_type dst_client;
+	enum ipa_client_type client;
+	enum ipa_aggr_type aggr_type;
+	enum ipa_aggr_en aggr_en;
+	u32 channel_count;
+	u32 length_offset;
+	u32 header_align;
+	u32 header_offset;
+	u32 header_size;
+	u32 ep_id;
+	int ret;
+
+	client = IPA_CLIENT_APPS_WAN_PROD;
+	dst_client = IPA_CLIENT_APPS_LAN_CONS;
+	channel_count = IPA_APPS_WWAN_PROD_RING_COUNT;
+	header_size = sizeof(struct rmnet_map_header_s);
+	offload_type = IPA_CS_OFFLOAD_NONE;
+	aggr_en = IPA_BYPASS_AGGR;
+	aggr_type = 0;	/* ignored if BYPASS */
+	header_offset = 0;
+	length_offset = 0;
+	header_align = 0;
+
+	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM) {
+		offload_type = IPA_CS_OFFLOAD_UL;
+		header_offset = sizeof(struct rmnet_map_header_s) / 4;
+		header_size += sizeof(u32);
+	}
+
+	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION) {
+		aggr_en = IPA_ENABLE_DEAGGR;
+		aggr_type = IPA_QCMAP;
+		length_offset = offsetof(struct rmnet_map_header_s, pkt_len);
+		header_align = ilog2(sizeof(u32));
+	}
+
+	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	ret = ipa_ep_alloc(client);
+	if (ret < 0)
+		goto out_unlock;
+	ep_id = ret;
+
+	if (aggr_en == IPA_ENABLE_DEAGGR && !ipa_endp_aggr_support(ep_id)) {
+		ret = -ENOTSUPP;
+		goto out_unlock;
+	}
+
+	/* We really do want 0 metadata offset */
+	ipa_endp_init_hdr_prod(ep_id, header_size, 0, length_offset);
+	ipa_endp_init_hdr_ext_prod(ep_id, header_align);
+	ipa_endp_init_mode_prod(ep_id, IPA_BASIC, dst_client);
+	ipa_endp_init_aggr_prod(ep_id, aggr_en, aggr_type);
+	ipa_endp_init_cfg_prod(ep_id, offload_type, header_offset);
+	ipa_endp_init_seq_prod(ep_id);
+	ipa_endp_init_deaggr_prod(ep_id);
+	/* Enable source notification status for exception packets
+	 * (i.e. QMAP commands) to be routed to modem.
+	 */
+	ipa_endp_status_prod(ep_id, true, IPA_CLIENT_Q6_WAN_CONS);
+
+	/* Use a deferred interrupting no-op to reduce completion interrupts */
+	ipa_no_intr_init(ep_id);
+
+	ret = ipa_ep_setup(ep_id, channel_count, 1, 0,
+			   apps_ipa_tx_complete_notify, dev);
+	if (ret)
+		ipa_ep_free(ep_id);
+	else
+		rmnet_ipa_ctx->wan_prod_ep_id = ep_id;
+
+out_unlock:
+	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	return ret;
+}
+
+/** ipa_wwan_add_mux_channel() - add a mux_id */
+static int ipa_wwan_add_mux_channel(u32 mux_id)
+{
+	int ret;
+	u32 i;
+
+	mutex_lock(&rmnet_ipa_ctx->mux_id_mutex);
+
+	if (rmnet_ipa_ctx->mux_id_count >= MUX_CHANNEL_MAX) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	for (i = 0; i < rmnet_ipa_ctx->mux_id_count; i++)
+		if (mux_id == rmnet_ipa_ctx->mux_id[i])
+			break;
+
+	/* Record the mux_id if it hasn't already been seen */
+	if (i == rmnet_ipa_ctx->mux_id_count)
+		rmnet_ipa_ctx->mux_id[rmnet_ipa_ctx->mux_id_count++] = mux_id;
+	ret = 0;
+out:
+	mutex_unlock(&rmnet_ipa_ctx->mux_id_mutex);
+
+	return ret;
+}
+
+/** ipa_wwan_ioctl_extended() - rmnet extended I/O control */
+static int ipa_wwan_ioctl_extended(struct net_device *dev, void __user *data)
+{
+	struct rmnet_ioctl_extended_s edata = { };
+	size_t size = sizeof(edata);
+
+	if (copy_from_user(&edata, data, size))
+		return -EFAULT;
+
+	switch (edata.extended_ioctl) {
+	case RMNET_IOCTL_GET_SUPPORTED_FEATURES:	/* Get features */
+		edata.u.data = RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL;
+		edata.u.data |= RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT;
+		edata.u.data |= RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT;
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_EPID:			/* Get endpoint ID */
+		edata.u.data = 1;
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_DRIVER_NAME:		/* Get driver name */
+		memcpy(&edata.u.if_name, rmnet_ipa_ctx->dev->name, IFNAMSIZ);
+		goto copy_out;
+
+	case RMNET_IOCTL_ADD_MUX_CHANNEL:		/* Add MUX ID */
+		return ipa_wwan_add_mux_channel(edata.u.rmnet_mux_val.mux_id);
+
+	case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:	/* Egress data format */
+		return handle_egress_format(dev, &edata) ? -EFAULT : 0;
+
+	case RMNET_IOCTL_SET_INGRESS_DATA_FORMAT:	/* Ingress format */
+		return handle_ingress_format(dev, &edata) ? -EFAULT : 0;
+
+	case RMNET_IOCTL_GET_EP_PAIR:			/* Get endpoint pair */
+		edata.u.ipa_ep_pair.consumer_pipe_num =
+				ipa_client_ep_id(IPA_CLIENT_APPS_WAN_PROD);
+		edata.u.ipa_ep_pair.producer_pipe_num =
+				ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS);
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_SG_SUPPORT:		/* Get SG support */
+		edata.u.data = 1;	/* Scatter/gather is always supported */
+		goto copy_out;
+
+	/* Unsupported requests */
+	case RMNET_IOCTL_SET_MRU:			/* Set MRU */
+	case RMNET_IOCTL_GET_MRU:			/* Get MRU */
+	case RMNET_IOCTL_GET_AGGREGATION_COUNT:		/* Get agg count */
+	case RMNET_IOCTL_SET_AGGREGATION_COUNT:		/* Set agg count */
+	case RMNET_IOCTL_GET_AGGREGATION_SIZE:		/* Get agg size */
+	case RMNET_IOCTL_SET_AGGREGATION_SIZE:		/* Set agg size */
+	case RMNET_IOCTL_FLOW_CONTROL:			/* Do flow control */
+	case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:	/* For legacy use */
+	case RMNET_IOCTL_GET_HWSW_MAP:			/* Get HW/SW map */
+	case RMNET_IOCTL_SET_RX_HEADROOM:		/* Set RX Headroom */
+	case RMNET_IOCTL_SET_QOS_VERSION:		/* Set 8/6 byte QoS */
+	case RMNET_IOCTL_GET_QOS_VERSION:		/* Get 8/6 byte QoS */
+	case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:	/* Get QoS modes */
+	case RMNET_IOCTL_SET_SLEEP_STATE:		/* Set sleep state */
+	case RMNET_IOCTL_SET_XLAT_DEV_INFO:		/* xlat dev name */
+	case RMNET_IOCTL_DEREGISTER_DEV:		/* Deregister netdev */
+		return -ENOTSUPP;	/* Defined, but unsupported command */
+
+	default:
+		return -EINVAL;		/* Invalid (unrecognized) command */
+	}
+
+copy_out:
+	return copy_to_user(data, &edata, size) ? -EFAULT : 0;
+}
+
+/** ipa_wwan_ioctl() - I/O control for wwan network driver */
+static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct rmnet_ioctl_data_s ioctl_data = { };
+	void __user *data;
+	size_t size;
+
+	data = ifr->ifr_ifru.ifru_data;
+	size = sizeof(ioctl_data);
+
+	switch (cmd) {
+	/* These features are implied; alternatives are not supported */
+	case RMNET_IOCTL_SET_LLP_IP:		/* RAW IP protocol */
+	case RMNET_IOCTL_SET_QOS_DISABLE:	/* QoS header disabled */
+		return 0;
+
+	/* These features are not supported; use alternatives */
+	case RMNET_IOCTL_SET_LLP_ETHERNET:	/* Ethernet protocol */
+	case RMNET_IOCTL_SET_QOS_ENABLE:	/* QoS header enabled */
+	case RMNET_IOCTL_GET_OPMODE:		/* Get operation mode */
+	case RMNET_IOCTL_FLOW_ENABLE:		/* Flow enable */
+	case RMNET_IOCTL_FLOW_DISABLE:		/* Flow disable */
+	case RMNET_IOCTL_FLOW_SET_HNDL:		/* Set flow handle */
+		return -ENOTSUPP;
+
+	case RMNET_IOCTL_GET_LLP:		/* Get link protocol */
+		ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
+		goto copy_out;
+
+	case RMNET_IOCTL_GET_QOS:		/* Get QoS header state */
+		ioctl_data.u.operation_mode = RMNET_MODE_NONE;
+		goto copy_out;
+
+	case RMNET_IOCTL_OPEN:			/* Open transport port */
+	case RMNET_IOCTL_CLOSE:			/* Close transport port */
+		return 0;
+
+	case RMNET_IOCTL_EXTENDED:		/* Extended IOCTLs */
+		return ipa_wwan_ioctl_extended(dev, data);
+
+	default:
+		return -EINVAL;
+	}
+
+copy_out:
+	return copy_to_user(data, &ioctl_data, size) ? -EFAULT : 0;
+}
+
+static const struct net_device_ops ipa_wwan_ops_ip = {
+	.ndo_open	= ipa_wwan_open,
+	.ndo_stop	= ipa_wwan_stop,
+	.ndo_start_xmit	= ipa_wwan_xmit,
+	.ndo_do_ioctl	= ipa_wwan_ioctl,
+};
+
+/** wwan_setup() - Setup the wwan network driver */
+static void ipa_wwan_setup(struct net_device *dev)
+{
+	dev->netdev_ops = &ipa_wwan_ops_ip;
+	ether_setup(dev);
+	dev->header_ops = NULL;	 /* No header (override ether_setup() value) */
+	dev->type = ARPHRD_RAWIP;
+	dev->hard_header_len = 0;
+	dev->max_mtu = WWAN_DATA_LEN;
+	dev->mtu = dev->max_mtu;
+	dev->addr_len = 0;
+	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
+	dev->needed_headroom = HEADROOM_FOR_QMAP;
+	dev->needed_tailroom = TAILROOM;
+	dev->watchdog_timeo = msecs_to_jiffies(10 * MSEC_PER_SEC);
+}
+
+/** ipa_wwan_probe() - Network probe function */
+static int ipa_wwan_probe(struct platform_device *pdev)
+{
+	struct ipa_wwan_private *wwan_ptr;
+	struct net_device *dev;
+	int ret;
+
+	mutex_init(&rmnet_ipa_ctx->ep_setup_mutex);
+	mutex_init(&rmnet_ipa_ctx->mux_id_mutex);
+
+	/* Mark client handles bad until we initialize them */
+	rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
+	rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
+
+	ret = ipa_modem_smem_init();
+	if (ret)
+		goto err_clear_ctx;
+
+	/* start A7 QMI service/client */
+	ipa_qmi_init();
+
+	/* initialize wan-driver netdev */
+	dev = alloc_netdev(sizeof(struct ipa_wwan_private),
+			   IPA_WWAN_DEV_NAME,
+			   NET_NAME_UNKNOWN,
+			   ipa_wwan_setup);
+	if (!dev) {
+		ipa_err("no memory for netdev\n");
+		ret = -ENOMEM;
+		goto err_clear_ctx;
+	}
+	rmnet_ipa_ctx->dev = dev;
+	wwan_ptr = netdev_priv(dev);
+	wwan_ptr->outstanding_high_ctl = DEFAULT_OUTSTANDING_HIGH_CTL;
+	wwan_ptr->outstanding_high = DEFAULT_OUTSTANDING_HIGH;
+	wwan_ptr->outstanding_low = DEFAULT_OUTSTANDING_LOW;
+	atomic_set(&wwan_ptr->outstanding_pkts, 0);
+
+	/* Enable SG support in netdevice. */
+	dev->hw_features |= NETIF_F_SG;
+
+	netif_napi_add(dev, &wwan_ptr->napi, ipa_rmnet_poll, NAPI_WEIGHT);
+	ret = register_netdev(dev);
+	if (ret) {
+		ipa_err("unable to register ipa_netdev %d rc=%d\n", 0, ret);
+		goto err_napi_del;
+	}
+
+	/* offline charging mode */
+	ipa_proxy_clk_unvote();
+
+	/* Till the system is suspended, we keep the clock open */
+	ipa_client_add();
+
+	initialized = true;
+
+	return 0;
+
+err_napi_del:
+	netif_napi_del(&wwan_ptr->napi);
+	free_netdev(dev);
+err_clear_ctx:
+	memset(&rmnet_ipa_ctx_struct, 0, sizeof(rmnet_ipa_ctx_struct));
+
+	return ret;
+}
+
+static int ipa_wwan_remove(struct platform_device *pdev)
+{
+	struct ipa_wwan_private *wwan_ptr = netdev_priv(rmnet_ipa_ctx->dev);
+
+	dev_info(&pdev->dev, "rmnet_ipa started deinitialization\n");
+
+	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	ipa_client_add();
+
+	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_teardown(rmnet_ipa_ctx->wan_cons_ep_id);
+		rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
+	}
+
+	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
+		ipa_ep_teardown(rmnet_ipa_ctx->wan_prod_ep_id);
+		rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
+	}
+
+	ipa_client_remove();
+
+	netif_napi_del(&wwan_ptr->napi);
+	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
+	unregister_netdev(rmnet_ipa_ctx->dev);
+
+	if (rmnet_ipa_ctx->dev)
+		free_netdev(rmnet_ipa_ctx->dev);
+	rmnet_ipa_ctx->dev = NULL;
+
+	mutex_destroy(&rmnet_ipa_ctx->mux_id_mutex);
+	mutex_destroy(&rmnet_ipa_ctx->ep_setup_mutex);
+
+	initialized = false;
+
+	dev_info(&pdev->dev, "rmnet_ipa completed deinitialization\n");
+
+	return 0;
+}
+
+/** rmnet_ipa_ap_suspend() - suspend callback for runtime_pm
+ * @dev: pointer to device
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP suspend
+ * operation is invoked, usually by pressing a suspend button.
+ *
+ * Returns -EAGAIN to runtime_pm framework in case there are pending packets
+ * in the Tx queue. This will postpone the suspend operation until all the
+ * pending packets will be transmitted.
+ *
+ * In case there are no packets to send, releases the WWAN0_PROD entity.
+ * As an outcome, the number of IPA active clients should be decremented
+ * until IPA clocks can be gated.
+ */
+static int rmnet_ipa_ap_suspend(struct device *dev)
+{
+	struct net_device *netdev = rmnet_ipa_ctx->dev;
+	struct ipa_wwan_private *wwan_ptr;
+	int ret;
+
+	if (!netdev) {
+		ipa_err("netdev is NULL.\n");
+		ret = 0;
+		goto bail;
+	}
+
+	netif_tx_lock_bh(netdev);
+	wwan_ptr = netdev_priv(netdev);
+	if (!wwan_ptr) {
+		ipa_err("wwan_ptr is NULL.\n");
+		ret = 0;
+		goto unlock_and_bail;
+	}
+
+	/* Do not allow A7 to suspend in case there are outstanding packets */
+	if (atomic_read(&wwan_ptr->outstanding_pkts) != 0) {
+		ret = -EAGAIN;
+		goto unlock_and_bail;
+	}
+
+	/* Make sure that there is no Tx operation ongoing */
+	netif_stop_queue(netdev);
+
+	ret = 0;
+	ipa_client_remove();
+
+unlock_and_bail:
+	netif_tx_unlock_bh(netdev);
+bail:
+
+	return ret;
+}
+
+/** rmnet_ipa_ap_resume() - resume callback for runtime_pm
+ * @dev: pointer to device
+ *
+ * This callback will be invoked by the runtime_pm framework when an AP resume
+ * operation is invoked.
+ *
+ * Enables the network interface queue and returns success to the
+ * runtime_pm framework.
+ */
+static int rmnet_ipa_ap_resume(struct device *dev)
+{
+	struct net_device *netdev = rmnet_ipa_ctx->dev;
+
+	ipa_client_add();
+	if (netdev)
+		netif_wake_queue(netdev);
+
+	return 0;
+}
+
+static const struct of_device_id rmnet_ipa_dt_match[] = {
+	{.compatible = "qcom,rmnet-ipa"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, rmnet_ipa_dt_match);
+
+static const struct dev_pm_ops rmnet_ipa_pm_ops = {
+	.suspend_noirq = rmnet_ipa_ap_suspend,
+	.resume_noirq = rmnet_ipa_ap_resume,
+};
+
+static struct platform_driver rmnet_ipa_driver = {
+	.driver = {
+		.name = "rmnet_ipa",
+		.owner = THIS_MODULE,
+		.pm = &rmnet_ipa_pm_ops,
+		.of_match_table = rmnet_ipa_dt_match,
+	},
+	.probe = ipa_wwan_probe,
+	.remove = ipa_wwan_remove,
+};
+
+int ipa_wwan_init(void)
+{
+	if (initialized)
+		return 0;
+
+	return platform_driver_register(&rmnet_ipa_driver);
+}
+
+void ipa_wwan_cleanup(void)
+{
+	platform_driver_unregister(&rmnet_ipa_driver);
+	memset(&rmnet_ipa_ctx_struct, 0, sizeof(rmnet_ipa_ctx_struct));
+}
+
+static int ipa_rmnet_poll(struct napi_struct *napi, int budget)
+{
+	return ipa_rx_poll(rmnet_ipa_ctx->wan_cons_ep_id, budget);
+}
+
+MODULE_DESCRIPTION("WWAN Network Interface");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07  0:32   ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

This last IPA code patch includes "Kconfig" for kernel configuration
and a "Makefile" to build the code.  The main configuration option
enabling the code to be built is "CONFIG_IPA".  A second one,
"CONFIG_IPA_ASSERT", is on by default if CONFIG_IPA is selected, but
can be disabled to cause ipa_assert() calls to not be included.

Finally, "ipa_i.h" is a sort of dumping ground header file, declaring
things used and needed throughout the code.  (I expect much of this can
be doled out into separate headers.)

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/Kconfig  |  30 ++
 drivers/net/ipa/Makefile |   7 +
 drivers/net/ipa/ipa_i.h  | 573 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 610 insertions(+)
 create mode 100644 drivers/net/ipa/Kconfig
 create mode 100644 drivers/net/ipa/Makefile
 create mode 100644 drivers/net/ipa/ipa_i.h

diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
new file mode 100644
index 000000000000..f8ea9363f532
--- /dev/null
+++ b/drivers/net/ipa/Kconfig
@@ -0,0 +1,30 @@
+config IPA
+	tristate "Qualcomm IPA support"
+	depends on NET
+	select QCOM_QMI_HELPERS
+	select QCOM_MDT_LOADER
+	default n
+	help
+	  Choose Y here to include support for the Qualcomm IP
+	  Accelerator (IPA), a hardware block present in some
+	  Qualcomm SoCs.  The IPA is a programmable protocol
+	  processor that is capable of generic hardware handling
+	  of IP packets, including routing, filtering, and NAT.
+	  Currently the IPA driver supports only basic transport
+	  of network traffic between the AP and modem, on the
+	  Qualcomm SDM845 SoC.
+
+	  If unsure, say N.
+
+config IPA_ASSERT
+	bool "Enable IPA assertions"
+	depends on IPA
+	default y
+	help
+	 Incorporate IPA assertion verification in the build.  This
+	 cause various design assumptions to be checked at runtime,
+	 generating a report (and a crash) if any assumed condition
+	 does not hold.  You may wish to disable this to avoid the
+	 overhead of checking.
+
+	 If unsure doubt, say "Y" here.
diff --git a/drivers/net/ipa/Makefile b/drivers/net/ipa/Makefile
new file mode 100644
index 000000000000..6b1de4ab2dad
--- /dev/null
+++ b/drivers/net/ipa/Makefile
@@ -0,0 +1,7 @@
+obj-$(CONFIG_IPA)	+=	ipa.o
+
+ipa-y			:=	ipa_main.o ipa_dp.o \
+				ipa_utils.o ipa_interrupts.o \
+				ipa_uc.o gsi.o rmnet_ipa.o \
+				ipa_qmi.o ipa_qmi_msg.o \
+				ipahal.o ipa_reg.o ipa_dma.o
diff --git a/drivers/net/ipa/ipa_i.h b/drivers/net/ipa/ipa_i.h
new file mode 100644
index 000000000000..efbb2cb7177f
--- /dev/null
+++ b/drivers/net/ipa/ipa_i.h
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_I_H_
+#define _IPA_I_H_
+
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/bug.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/interconnect.h>
+#include <linux/pm_wakeup.h>
+#include <linux/skbuff.h>
+
+#include "ipa_dma.h"
+#include "ipa_reg.h"
+#include "gsi.h"
+
+#define IPA_MTU				1500
+
+#define IPA_EP_COUNT_MAX		31
+#define IPA_LAN_RX_HEADER_LENGTH	0
+#define IPA_DL_CHECKSUM_LENGTH		8
+#define IPA_GENERIC_RX_POOL_SZ		192
+
+#define IPA_GENERIC_AGGR_BYTE_LIMIT	(6 * SZ_1K)	/* bytes */
+#define IPA_GENERIC_AGGR_TIME_LIMIT	1		/* milliseconds */
+#define IPA_GENERIC_AGGR_PKT_LIMIT	0
+
+#define IPA_MAX_STATUS_STAT_NUM		30
+
+/* An explicitly bad endpoint identifier value */
+#define IPA_EP_ID_BAD			(~(u32)0)
+
+#define IPA_MEM_CANARY_VAL		0xdeadbeef
+
+#define IPA_GSI_CHANNEL_STOP_MAX_RETRY	10
+#define IPA_GSI_CHANNEL_STOP_PKT_SIZE	1
+
+/**
+ * DOC:
+ * The IPA has a block of shared memory, divided into regions used for
+ * specific purposes.  Values below define this layout (i.e., the
+ * sizes and locations of all these regions).  One or two "canary"
+ * values sit between some regions, as a check for erroneous writes
+ * outside a region.  There are combinations of and routing tables,
+ * covering IPv4 and IPv6, and for each of those, hashed and
+ * non-hashed variants.  About half of routing table entries are
+ * reserved for modem use.
+ */
+
+/* The maximum number of filter table entries (IPv4, IPv6; hashed and not) */
+#define IPA_MEM_FLT_COUNT	14
+
+/* The number of routing table entries (IPv4, IPv6; hashed and not) */
+#define IPA_MEM_RT_COUNT			15
+
+ /* Which routing table entries are for the modem */
+#define IPA_MEM_MODEM_RT_COUNT			8
+#define IPA_MEM_MODEM_RT_INDEX_MIN		0
+#define IPA_MEM_MODEM_RT_INDEX_MAX \
+               (IPA_MEM_MODEM_RT_INDEX_MIN + IPA_MEM_MODEM_RT_COUNT - 1)
+
+#define IPA_MEM_V4_FLT_HASH_OFST		0x288
+#define IPA_MEM_V4_FLT_NHASH_OFST		0x308
+#define IPA_MEM_V6_FLT_HASH_OFST		0x388
+#define IPA_MEM_V6_FLT_NHASH_OFST		0x408
+#define IPA_MEM_V4_RT_HASH_OFST			0x488
+#define IPA_MEM_V4_RT_NHASH_OFST		0x508
+#define IPA_MEM_V6_RT_HASH_OFST			0x588
+#define IPA_MEM_V6_RT_NHASH_OFST		0x608
+#define IPA_MEM_MODEM_HDR_OFST			0x688
+#define IPA_MEM_MODEM_HDR_SIZE			0x140
+#define IPA_MEM_APPS_HDR_OFST			0x7c8
+#define IPA_MEM_APPS_HDR_SIZE			0x0
+#define IPA_MEM_MODEM_HDR_PROC_CTX_OFST		0x7d0
+#define IPA_MEM_MODEM_HDR_PROC_CTX_SIZE		0x200
+#define IPA_MEM_APPS_HDR_PROC_CTX_OFST		0x9d0
+#define IPA_MEM_APPS_HDR_PROC_CTX_SIZE		0x200
+#define IPA_MEM_MODEM_OFST			0xbd8
+#define IPA_MEM_MODEM_SIZE			0x1024
+#define IPA_MEM_END_OFST			0x2000
+#define IPA_MEM_UC_EVENT_RING_OFST		0x1c00	/* v3.5 and later */
+
+#define ipa_debug(fmt, args...)	dev_dbg(ipa_ctx->dev, fmt, ## args)
+#define ipa_err(fmt, args...)	dev_err(ipa_ctx->dev, fmt, ## args)
+
+#define ipa_bug() \
+	do {								\
+		ipa_err("an unrecoverable error has occurred\n");	\
+		BUG();							\
+	} while (0)
+
+#define ipa_bug_on(condition)						\
+	do {								\
+		if (condition) {				\
+			ipa_err("ipa_bug_on(%s) failed!\n", #condition); \
+			ipa_bug();					\
+		}							\
+	} while (0)
+
+#ifdef CONFIG_IPA_ASSERT
+
+/* Communicate a condition assumed by the code.  This is intended as
+ * an informative statement about something that should always be true.
+ *
+ * N.B.:  Conditions asserted must not incorporate code with side-effects
+ *	  that are necessary for correct execution.  And an assertion
+ *	  failure should not be expected to force a crash (because all
+ *	  assertion code is optionally compiled out).
+ */
+#define ipa_assert(cond) \
+	do {								\
+		if (!(cond)) {				\
+			ipa_err("ipa_assert(%s) failed!\n", #cond);	\
+			ipa_bug();					\
+		}							\
+	} while (0)
+#else	/* !CONFIG_IPA_ASSERT */
+
+#define ipa_assert(expr)	((void)0)
+
+#endif	/* !CONFIG_IPA_ASSERT */
+
+enum ipa_ees {
+	IPA_EE_AP	= 0,
+	IPA_EE_Q6	= 1,
+	IPA_EE_UC	= 2,
+};
+
+/**
+ * enum ipa_client_type - names for the various IPA "clients"
+ *
+ * These are from the perspective of the clients, e.g. HSIC1_PROD
+ * means HSIC client is the producer and IPA is the consumer.
+ * PROD clients are always even, and CONS clients are always odd.
+ */
+enum ipa_client_type {
+	IPA_CLIENT_WLAN1_PROD                   = 10,
+	IPA_CLIENT_WLAN1_CONS                   = 11,
+
+	IPA_CLIENT_WLAN2_CONS                   = 13,
+
+	IPA_CLIENT_WLAN3_CONS                   = 15,
+
+	IPA_CLIENT_USB_PROD                     = 18,
+	IPA_CLIENT_USB_CONS                     = 19,
+
+	IPA_CLIENT_USB_DPL_CONS                 = 27,
+
+	IPA_CLIENT_APPS_LAN_PROD		= 32,
+	IPA_CLIENT_APPS_LAN_CONS		= 33,
+
+	IPA_CLIENT_APPS_WAN_PROD		= 34,
+	IPA_CLIENT_APPS_WAN_CONS		= 35,
+
+	IPA_CLIENT_APPS_CMD_PROD		= 36,
+
+	IPA_CLIENT_Q6_LAN_PROD			= 50,
+	IPA_CLIENT_Q6_LAN_CONS			= 51,
+
+	IPA_CLIENT_Q6_WAN_PROD			= 52,
+	IPA_CLIENT_Q6_WAN_CONS			= 53,
+
+	IPA_CLIENT_Q6_CMD_PROD			= 54,
+
+	IPA_CLIENT_TEST_PROD                    = 62,
+	IPA_CLIENT_TEST_CONS                    = 63,
+
+	IPA_CLIENT_TEST1_PROD                   = 64,
+	IPA_CLIENT_TEST1_CONS                   = 65,
+
+	IPA_CLIENT_TEST2_PROD                   = 66,
+	IPA_CLIENT_TEST2_CONS                   = 67,
+
+	IPA_CLIENT_TEST3_PROD                   = 68,
+	IPA_CLIENT_TEST3_CONS                   = 69,
+
+	IPA_CLIENT_TEST4_PROD                   = 70,
+	IPA_CLIENT_TEST4_CONS                   = 71,
+
+	IPA_CLIENT_DUMMY_CONS			= 73,
+
+	IPA_CLIENT_MAX,
+};
+
+static inline bool ipa_producer(enum ipa_client_type client)
+{
+	return !((u32)client & 1);	/* Even numbers are producers */
+}
+
+static inline bool ipa_consumer(enum ipa_client_type client)
+{
+	return !ipa_producer(client);
+}
+
+static inline bool ipa_modem_consumer(enum ipa_client_type client)
+{
+	return client == IPA_CLIENT_Q6_LAN_CONS ||
+		client == IPA_CLIENT_Q6_WAN_CONS;
+}
+
+static inline bool ipa_modem_producer(enum ipa_client_type client)
+{
+	return client == IPA_CLIENT_Q6_LAN_PROD ||
+		client == IPA_CLIENT_Q6_WAN_PROD ||
+		client == IPA_CLIENT_Q6_CMD_PROD;
+}
+
+static inline bool ipa_ap_consumer(enum ipa_client_type client)
+{
+	return client == IPA_CLIENT_APPS_LAN_CONS ||
+		client == IPA_CLIENT_APPS_WAN_CONS;
+}
+
+/**
+ * enum ipa_irq_type - IPA Interrupt Type
+ *
+ * Used to register handlers for IPA interrupts.
+ */
+enum ipa_irq_type {
+	IPA_INVALID_IRQ = 0,
+	IPA_UC_IRQ_0,
+	IPA_UC_IRQ_1,
+	IPA_TX_SUSPEND_IRQ,
+	IPA_IRQ_MAX
+};
+
+/**
+ * typedef ipa_irq_handler_t - irq handler/callback type
+ * @param ipa_irq_type		- interrupt type
+ * @param interrupt_data	- interrupt information data
+ *
+ * Callback function registered by ipa_add_interrupt_handler() to
+ * handle a specific interrupt type
+ */
+typedef void (*ipa_irq_handler_t)(enum ipa_irq_type interrupt,
+				  u32 interrupt_data);
+
+/**
+ * struct ipa_tx_suspend_irq_data - Interrupt data for IPA_TX_SUSPEND_IRQ
+ * @endpoints:	Bitmask of endpoints which cause IPA_TX_SUSPEND_IRQ interrupt
+ */
+struct ipa_tx_suspend_irq_data {
+	u32 endpoints;
+};
+
+/**
+ * enum ipa_dp_evt_type - Data path event type
+ */
+enum ipa_dp_evt_type {
+	IPA_RECEIVE,
+	IPA_WRITE_DONE,
+	IPA_CLIENT_START_POLL,
+	IPA_CLIENT_COMP_NAPI,
+};
+
+typedef void (*ipa_notify_cb)(void *priv, enum ipa_dp_evt_type evt,
+			      unsigned long data);
+
+/**
+ * struct ipa_ep_context - IPA end point context
+ * @allocated:	True when the endpoint has been allocated
+ * @client:	Client associated with the endpoint
+ * @channel_id:	EP's GSI channel
+ * @evt_ring_id: EP's GSI channel event ring
+ * @priv:	Pointer supplied when client_notify is called
+ *	  notified for new data avail
+ * @client_notify: Function called for event notification
+ * @napi_enabled: Endpoint uses NAPI
+ */
+struct ipa_ep_context {
+	bool allocated;
+	enum ipa_client_type client;
+	u32 channel_id;
+	u32 evt_ring_id;
+	bool bytes_xfered_valid;
+	u16 bytes_xfered;
+
+	struct ipa_reg_endp_init_hdr init_hdr;
+	struct ipa_reg_endp_init_hdr_ext hdr_ext;
+	struct ipa_reg_endp_init_mode init_mode;
+	struct ipa_reg_endp_init_aggr init_aggr;
+	struct ipa_reg_endp_init_cfg init_cfg;
+	struct ipa_reg_endp_init_seq init_seq;
+	struct ipa_reg_endp_init_deaggr init_deaggr;
+	struct ipa_reg_endp_init_hdr_metadata_mask metadata_mask;
+	struct ipa_reg_endp_status status;
+
+	void (*client_notify)(void *priv, enum ipa_dp_evt_type evt,
+			      unsigned long data);
+	void *priv;
+	bool napi_enabled;
+	struct ipa_sys_context *sys;
+};
+
+/**
+ * enum ipa_desc_type - IPA decriptor type
+ */
+enum ipa_desc_type {
+	IPA_DATA_DESC,
+	IPA_DATA_DESC_SKB,
+	IPA_DATA_DESC_SKB_PAGED,
+	IPA_IMM_CMD_DESC,
+};
+
+/**
+ * struct ipa_desc - IPA descriptor
+ * @type:	Type of data in the descriptor
+ * @len_opcode: Length of the payload, or opcode for immediate commands
+ * @payload:	Points to descriptor payload (e.g., socket buffer)
+ * @callback:	Completion callback
+ * @user1:	Pointer data supplied to callback
+ * @user2:	Integer data supplied with callback
+ */
+struct ipa_desc {
+	enum ipa_desc_type type;
+	u16 len_opcode;
+	void *payload;
+	void (*callback)(void *user1, int user2);
+	void *user1;
+	int user2;
+};
+
+/**
+ * enum ipahal_imm_cmd:	IPA immediate commands
+ *
+ * All immediate commands are issued using the APPS_CMD_PROD
+ * endpoint.  The numeric values here are the opcodes for IPA v3.5.1
+ * hardware
+ */
+enum ipahal_imm_cmd {
+	IPA_IMM_CMD_IP_V4_FILTER_INIT		= 3,
+	IPA_IMM_CMD_IP_V6_FILTER_INIT		= 4,
+	IPA_IMM_CMD_IP_V4_ROUTING_INIT		= 7,
+	IPA_IMM_CMD_IP_V6_ROUTING_INIT		= 8,
+	IPA_IMM_CMD_HDR_INIT_LOCAL		= 9,
+	IPA_IMM_CMD_DMA_TASK_32B_ADDR		= 17,
+	IPA_IMM_CMD_DMA_SHARED_MEM		= 19,
+};
+
+/**
+ * struct ipa_transport_pm - Transport power management data
+ * @dec_clients:	?
+ * @transport_pm_mutex:	Mutex to protect the transport_pm functionality.
+ */
+struct ipa_transport_pm {
+	atomic_t dec_clients;
+	struct mutex transport_pm_mutex;	/* XXX comment this */
+};
+
+struct ipa_smp2p_info {
+	struct qcom_smem_state *valid_state;
+	struct qcom_smem_state *enabled_state;
+	unsigned int valid_bit;
+	unsigned int enabled_bit;
+	unsigned int clock_query_irq;
+	unsigned int post_init_irq;
+	bool ipa_clk_on;
+	bool res_sent;
+};
+
+struct ipa_dma_task_info {
+	struct ipa_dma_mem mem;
+	void *payload;
+};
+
+/**
+ * struct ipa_context - IPA context
+ * @filter_bitmap:	End-points supporting filtering bitmap
+ * @ipa_irq:		IRQ number used for IPA
+ * @ipa_phys:		Physical address of IPA register memory
+ * @gsi:		Pointer to GSI structure
+ * @dev:		IPA device structure
+ * @ep:			Endpoint array
+ * @dp:			Data path information
+ * @smem_size:		Size of shared memory
+ * @smem_offset:	Offset of the usable area in shared memory
+ * @active_clients_mutex: Used when active clients count changes from/to 0
+ * @active_clients_count: Active client count
+ * @power_mgmt_wq:	Workqueue for power management
+ * @transport_pm:	Transport power management related information
+ * @cmd_prod_ep_id:	Endpoint for APPS_CMD_PROD
+ * @lan_cons_ep_id:	Endpoint for APPS_LAN_CONS
+ * @memory_path:	Path for memory interconnect
+ * @imem_path:		Path for internal memory interconnect
+ * @config_path:	Path for configuration interconnect
+ * @modem_clk_vote_valid: Whether proxy clock vote is held for modem
+ * @ep_count:		Number of endpoints available in hardware
+ * @uc_ctx:		Microcontroller context
+ * @wakeup_lock:	Lock protecting updates to wakeup_count
+ * @wakeup_count:	Count of times wakelock is acquired
+ * @wakeup:		Wakeup source
+ * @ipa_client_apps_wan_cons_agg_gro: APPS_WAN_CONS generic receive offload
+ * @smp2p_info:		Information related to SMP2P
+ * @dma_task_info:	Preallocated DMA task
+ */
+struct ipa_context {
+	u32 filter_bitmap;
+	u32 ipa_irq;
+	phys_addr_t ipa_phys;
+	struct gsi *gsi;
+	struct device *dev;
+
+	struct ipa_ep_context ep[IPA_EP_COUNT_MAX];
+	struct ipa_dp *dp;
+	u32 smem_size;
+	u16 smem_offset;
+	struct mutex active_clients_mutex;	/* count changes from/to 0 */
+	atomic_t active_clients_count;
+	struct workqueue_struct *power_mgmt_wq;
+	struct ipa_transport_pm transport_pm;
+	u32 cmd_prod_ep_id;
+	u32 lan_cons_ep_id;
+	struct icc_path *memory_path;
+	struct icc_path *imem_path;
+	struct icc_path *config_path;
+	struct clk *core_clock;
+	bool modem_clk_vote_valid;
+	u32 ep_count;
+
+	struct ipa_uc_ctx *uc_ctx;
+
+	spinlock_t wakeup_lock;		/* protects updates to wakeup_count */
+	u32 wakeup_count;
+	struct wakeup_source wakeup;
+
+	/* RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA */
+	bool ipa_client_apps_wan_cons_agg_gro;
+	/* M-release support to know client endpoint */
+	struct ipa_smp2p_info smp2p_info;
+	struct ipa_dma_task_info dma_task_info;
+};
+
+extern struct ipa_context *ipa_ctx;
+
+int ipa_wwan_init(void);
+void ipa_wwan_cleanup(void);
+
+int ipa_stop_gsi_channel(u32 ep_id);
+
+void ipa_cfg_ep(u32 ep_id);
+
+int ipa_tx_dp(enum ipa_client_type dst, struct sk_buff *skb);
+
+bool ipa_endp_aggr_support(u32 ep_id);
+enum ipa_seq_type ipa_endp_seq_type(u32 ep_id);
+
+void ipa_endp_init_hdr_cons(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset);
+void ipa_endp_init_hdr_prod(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset);
+void ipa_endp_init_hdr_ext_cons(u32 ep_id, u32 pad_align,
+				bool pad_included);
+void ipa_endp_init_hdr_ext_prod(u32 ep_id, u32 pad_align);
+void ipa_endp_init_mode_cons(u32 ep_id);
+void ipa_endp_init_mode_prod(u32 ep_id, enum ipa_mode mode,
+			     enum ipa_client_type dst_client);
+void ipa_endp_init_aggr_cons(u32 ep_id, u32 size, u32 count,
+			     bool close_on_eof);
+void ipa_endp_init_aggr_prod(u32 ep_id, enum ipa_aggr_en aggr_en,
+			     enum ipa_aggr_type aggr_type);
+void ipa_endp_init_cfg_cons(u32 ep_id,
+			    enum ipa_cs_offload_en offload_type);
+void ipa_endp_init_cfg_prod(u32 ep_id, enum ipa_cs_offload_en offload_type,
+			    u32 metadata_offset);
+void ipa_endp_init_seq_cons(u32 ep_id);
+void ipa_endp_init_seq_prod(u32 ep_id);
+void ipa_endp_init_deaggr_cons(u32 ep_id);
+void ipa_endp_init_deaggr_prod(u32 ep_id);
+void ipa_endp_init_hdr_metadata_mask_cons(u32 ep_id, u32 mask);
+void ipa_endp_init_hdr_metadata_mask_prod(u32 ep_id);
+void ipa_endp_status_cons(u32 ep_id, bool enable);
+void ipa_endp_status_prod(u32 ep_id, bool enable,
+			  enum ipa_client_type client);
+int ipa_ep_alloc(enum ipa_client_type client);
+void ipa_ep_free(u32 ep_id);
+
+void ipa_no_intr_init(u32 prod_ep_id);
+
+int ipa_ep_setup(u32 ep_id, u32 channel_count, u32 evt_ring_mult,
+		 u32 rx_buffer_size,
+		 void (*client_notify)(void *priv, enum ipa_dp_evt_type type,
+				       unsigned long data),
+		 void *priv);
+
+void ipa_ep_teardown(u32 ep_id);
+
+void ipa_rx_switch_to_poll_mode(struct ipa_sys_context *sys);
+
+void ipa_add_interrupt_handler(enum ipa_irq_type interrupt,
+			       ipa_irq_handler_t handler);
+
+void ipa_remove_interrupt_handler(enum ipa_irq_type interrupt);
+
+void ipa_proxy_clk_vote(void);
+void ipa_proxy_clk_unvote(void);
+
+u32 ipa_filter_bitmap_init(void);
+
+bool ipa_is_modem_ep(u32 ep_id);
+
+u32 ipa_client_ep_id(enum ipa_client_type client);
+u32 ipa_client_channel_id(enum ipa_client_type client);
+u32 ipa_client_tlv_count(enum ipa_client_type client);
+
+void ipa_init_hw(void);
+
+int ipa_interconnect_init(struct device *dev);
+void ipa_interconnect_exit(void);
+
+int ipa_interconnect_enable(void);
+int ipa_interconnect_disable(void);
+
+int ipa_send_cmd_timeout(struct ipa_desc *desc, u32 timeout);
+static inline int ipa_send_cmd(struct ipa_desc *desc)
+{
+	return ipa_send_cmd_timeout(desc, 0);
+}
+
+void ipa_client_add(void);
+bool ipa_client_add_additional(void);
+void ipa_client_remove(void);
+
+u32 ipa_aggr_byte_limit_buf_size(u32 byte_limit);
+
+void ipa_cfg_default_route(enum ipa_client_type client);
+
+int ipa_interrupts_init(void);
+
+void ipa_suspend_active_aggr_wa(u32 ep_id);
+void ipa_lan_rx_cb(void *priv, enum ipa_dp_evt_type evt, unsigned long data);
+
+void ipa_sram_settings_read(void);
+
+int ipa_modem_smem_init(void);
+
+struct ipa_uc_ctx *ipa_uc_init(phys_addr_t phys_addr);
+bool ipa_uc_loaded(void);
+void ipa_uc_panic_notifier(void);
+
+u32 ipa_get_ep_count(void);
+int ipa_ap_suspend(struct device *dev);
+int ipa_ap_resume(struct device *dev);
+void ipa_set_resource_groups_min_max_limits(void);
+void ipa_ep_suspend_all(void);
+void ipa_ep_resume_all(void);
+void ipa_inc_acquire_wakelock(void);
+void ipa_dec_release_wakelock(void);
+int ipa_rx_poll(u32 ep_id, int budget);
+void ipa_reset_freeze_vote(void);
+void ipa_enable_dcd(void);
+
+int ipa_gsi_dma_task_alloc(void);
+void ipa_gsi_dma_task_free(void);
+
+void ipa_set_flt_tuple_mask(u32 ep_id);
+void ipa_set_rt_tuple_mask(int tbl_idx);
+
+void ipa_gsi_irq_rx_notify_cb(void *chan_data, u16 count);
+void ipa_gsi_irq_tx_notify_cb(void *xfer_data);
+
+bool ipa_ep_polling(struct ipa_ep_context *ep);
+
+struct ipa_dp *ipa_dp_init(void);
+void ipa_dp_exit(struct ipa_dp *dp);
+
+#endif /* _IPA_I_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
@ 2018-11-07  0:32   ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-07  0:32 UTC (permalink / raw)
  To: linux-arm-kernel

This last IPA code patch includes "Kconfig" for kernel configuration
and a "Makefile" to build the code.  The main configuration option
enabling the code to be built is "CONFIG_IPA".  A second one,
"CONFIG_IPA_ASSERT", is on by default if CONFIG_IPA is selected, but
can be disabled to cause ipa_assert() calls to not be included.

Finally, "ipa_i.h" is a sort of dumping ground header file, declaring
things used and needed throughout the code.  (I expect much of this can
be doled out into separate headers.)

Signed-off-by: Alex Elder <elder@linaro.org>
---
 drivers/net/ipa/Kconfig  |  30 ++
 drivers/net/ipa/Makefile |   7 +
 drivers/net/ipa/ipa_i.h  | 573 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 610 insertions(+)
 create mode 100644 drivers/net/ipa/Kconfig
 create mode 100644 drivers/net/ipa/Makefile
 create mode 100644 drivers/net/ipa/ipa_i.h

diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
new file mode 100644
index 000000000000..f8ea9363f532
--- /dev/null
+++ b/drivers/net/ipa/Kconfig
@@ -0,0 +1,30 @@
+config IPA
+	tristate "Qualcomm IPA support"
+	depends on NET
+	select QCOM_QMI_HELPERS
+	select QCOM_MDT_LOADER
+	default n
+	help
+	  Choose Y here to include support for the Qualcomm IP
+	  Accelerator (IPA), a hardware block present in some
+	  Qualcomm SoCs.  The IPA is a programmable protocol
+	  processor that is capable of generic hardware handling
+	  of IP packets, including routing, filtering, and NAT.
+	  Currently the IPA driver supports only basic transport
+	  of network traffic between the AP and modem, on the
+	  Qualcomm SDM845 SoC.
+
+	  If unsure, say N.
+
+config IPA_ASSERT
+	bool "Enable IPA assertions"
+	depends on IPA
+	default y
+	help
+	 Incorporate IPA assertion verification in the build.  This
+	 cause various design assumptions to be checked at runtime,
+	 generating a report (and a crash) if any assumed condition
+	 does not hold.  You may wish to disable this to avoid the
+	 overhead of checking.
+
+	 If unsure doubt, say "Y" here.
diff --git a/drivers/net/ipa/Makefile b/drivers/net/ipa/Makefile
new file mode 100644
index 000000000000..6b1de4ab2dad
--- /dev/null
+++ b/drivers/net/ipa/Makefile
@@ -0,0 +1,7 @@
+obj-$(CONFIG_IPA)	+=	ipa.o
+
+ipa-y			:=	ipa_main.o ipa_dp.o \
+				ipa_utils.o ipa_interrupts.o \
+				ipa_uc.o gsi.o rmnet_ipa.o \
+				ipa_qmi.o ipa_qmi_msg.o \
+				ipahal.o ipa_reg.o ipa_dma.o
diff --git a/drivers/net/ipa/ipa_i.h b/drivers/net/ipa/ipa_i.h
new file mode 100644
index 000000000000..efbb2cb7177f
--- /dev/null
+++ b/drivers/net/ipa/ipa_i.h
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+#ifndef _IPA_I_H_
+#define _IPA_I_H_
+
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/bug.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/interconnect.h>
+#include <linux/pm_wakeup.h>
+#include <linux/skbuff.h>
+
+#include "ipa_dma.h"
+#include "ipa_reg.h"
+#include "gsi.h"
+
+#define IPA_MTU				1500
+
+#define IPA_EP_COUNT_MAX		31
+#define IPA_LAN_RX_HEADER_LENGTH	0
+#define IPA_DL_CHECKSUM_LENGTH		8
+#define IPA_GENERIC_RX_POOL_SZ		192
+
+#define IPA_GENERIC_AGGR_BYTE_LIMIT	(6 * SZ_1K)	/* bytes */
+#define IPA_GENERIC_AGGR_TIME_LIMIT	1		/* milliseconds */
+#define IPA_GENERIC_AGGR_PKT_LIMIT	0
+
+#define IPA_MAX_STATUS_STAT_NUM		30
+
+/* An explicitly bad endpoint identifier value */
+#define IPA_EP_ID_BAD			(~(u32)0)
+
+#define IPA_MEM_CANARY_VAL		0xdeadbeef
+
+#define IPA_GSI_CHANNEL_STOP_MAX_RETRY	10
+#define IPA_GSI_CHANNEL_STOP_PKT_SIZE	1
+
+/**
+ * DOC:
+ * The IPA has a block of shared memory, divided into regions used for
+ * specific purposes.  Values below define this layout (i.e., the
+ * sizes and locations of all these regions).  One or two "canary"
+ * values sit between some regions, as a check for erroneous writes
+ * outside a region.  There are combinations of and routing tables,
+ * covering IPv4 and IPv6, and for each of those, hashed and
+ * non-hashed variants.  About half of routing table entries are
+ * reserved for modem use.
+ */
+
+/* The maximum number of filter table entries (IPv4, IPv6; hashed and not) */
+#define IPA_MEM_FLT_COUNT	14
+
+/* The number of routing table entries (IPv4, IPv6; hashed and not) */
+#define IPA_MEM_RT_COUNT			15
+
+ /* Which routing table entries are for the modem */
+#define IPA_MEM_MODEM_RT_COUNT			8
+#define IPA_MEM_MODEM_RT_INDEX_MIN		0
+#define IPA_MEM_MODEM_RT_INDEX_MAX \
+               (IPA_MEM_MODEM_RT_INDEX_MIN + IPA_MEM_MODEM_RT_COUNT - 1)
+
+#define IPA_MEM_V4_FLT_HASH_OFST		0x288
+#define IPA_MEM_V4_FLT_NHASH_OFST		0x308
+#define IPA_MEM_V6_FLT_HASH_OFST		0x388
+#define IPA_MEM_V6_FLT_NHASH_OFST		0x408
+#define IPA_MEM_V4_RT_HASH_OFST			0x488
+#define IPA_MEM_V4_RT_NHASH_OFST		0x508
+#define IPA_MEM_V6_RT_HASH_OFST			0x588
+#define IPA_MEM_V6_RT_NHASH_OFST		0x608
+#define IPA_MEM_MODEM_HDR_OFST			0x688
+#define IPA_MEM_MODEM_HDR_SIZE			0x140
+#define IPA_MEM_APPS_HDR_OFST			0x7c8
+#define IPA_MEM_APPS_HDR_SIZE			0x0
+#define IPA_MEM_MODEM_HDR_PROC_CTX_OFST		0x7d0
+#define IPA_MEM_MODEM_HDR_PROC_CTX_SIZE		0x200
+#define IPA_MEM_APPS_HDR_PROC_CTX_OFST		0x9d0
+#define IPA_MEM_APPS_HDR_PROC_CTX_SIZE		0x200
+#define IPA_MEM_MODEM_OFST			0xbd8
+#define IPA_MEM_MODEM_SIZE			0x1024
+#define IPA_MEM_END_OFST			0x2000
+#define IPA_MEM_UC_EVENT_RING_OFST		0x1c00	/* v3.5 and later */
+
+#define ipa_debug(fmt, args...)	dev_dbg(ipa_ctx->dev, fmt, ## args)
+#define ipa_err(fmt, args...)	dev_err(ipa_ctx->dev, fmt, ## args)
+
+#define ipa_bug() \
+	do {								\
+		ipa_err("an unrecoverable error has occurred\n");	\
+		BUG();							\
+	} while (0)
+
+#define ipa_bug_on(condition)						\
+	do {								\
+		if (condition) {				\
+			ipa_err("ipa_bug_on(%s) failed!\n", #condition); \
+			ipa_bug();					\
+		}							\
+	} while (0)
+
+#ifdef CONFIG_IPA_ASSERT
+
+/* Communicate a condition assumed by the code.  This is intended as
+ * an informative statement about something that should always be true.
+ *
+ * N.B.:  Conditions asserted must not incorporate code with side-effects
+ *	  that are necessary for correct execution.  And an assertion
+ *	  failure should not be expected to force a crash (because all
+ *	  assertion code is optionally compiled out).
+ */
+#define ipa_assert(cond) \
+	do {								\
+		if (!(cond)) {				\
+			ipa_err("ipa_assert(%s) failed!\n", #cond);	\
+			ipa_bug();					\
+		}							\
+	} while (0)
+#else	/* !CONFIG_IPA_ASSERT */
+
+#define ipa_assert(expr)	((void)0)
+
+#endif	/* !CONFIG_IPA_ASSERT */
+
+enum ipa_ees {
+	IPA_EE_AP	= 0,
+	IPA_EE_Q6	= 1,
+	IPA_EE_UC	= 2,
+};
+
+/**
+ * enum ipa_client_type - names for the various IPA "clients"
+ *
+ * These are from the perspective of the clients, e.g. HSIC1_PROD
+ * means HSIC client is the producer and IPA is the consumer.
+ * PROD clients are always even, and CONS clients are always odd.
+ */
+enum ipa_client_type {
+	IPA_CLIENT_WLAN1_PROD                   = 10,
+	IPA_CLIENT_WLAN1_CONS                   = 11,
+
+	IPA_CLIENT_WLAN2_CONS                   = 13,
+
+	IPA_CLIENT_WLAN3_CONS                   = 15,
+
+	IPA_CLIENT_USB_PROD                     = 18,
+	IPA_CLIENT_USB_CONS                     = 19,
+
+	IPA_CLIENT_USB_DPL_CONS                 = 27,
+
+	IPA_CLIENT_APPS_LAN_PROD		= 32,
+	IPA_CLIENT_APPS_LAN_CONS		= 33,
+
+	IPA_CLIENT_APPS_WAN_PROD		= 34,
+	IPA_CLIENT_APPS_WAN_CONS		= 35,
+
+	IPA_CLIENT_APPS_CMD_PROD		= 36,
+
+	IPA_CLIENT_Q6_LAN_PROD			= 50,
+	IPA_CLIENT_Q6_LAN_CONS			= 51,
+
+	IPA_CLIENT_Q6_WAN_PROD			= 52,
+	IPA_CLIENT_Q6_WAN_CONS			= 53,
+
+	IPA_CLIENT_Q6_CMD_PROD			= 54,
+
+	IPA_CLIENT_TEST_PROD                    = 62,
+	IPA_CLIENT_TEST_CONS                    = 63,
+
+	IPA_CLIENT_TEST1_PROD                   = 64,
+	IPA_CLIENT_TEST1_CONS                   = 65,
+
+	IPA_CLIENT_TEST2_PROD                   = 66,
+	IPA_CLIENT_TEST2_CONS                   = 67,
+
+	IPA_CLIENT_TEST3_PROD                   = 68,
+	IPA_CLIENT_TEST3_CONS                   = 69,
+
+	IPA_CLIENT_TEST4_PROD                   = 70,
+	IPA_CLIENT_TEST4_CONS                   = 71,
+
+	IPA_CLIENT_DUMMY_CONS			= 73,
+
+	IPA_CLIENT_MAX,
+};
+
+static inline bool ipa_producer(enum ipa_client_type client)
+{
+	return !((u32)client & 1);	/* Even numbers are producers */
+}
+
+static inline bool ipa_consumer(enum ipa_client_type client)
+{
+	return !ipa_producer(client);
+}
+
+static inline bool ipa_modem_consumer(enum ipa_client_type client)
+{
+	return client == IPA_CLIENT_Q6_LAN_CONS ||
+		client == IPA_CLIENT_Q6_WAN_CONS;
+}
+
+static inline bool ipa_modem_producer(enum ipa_client_type client)
+{
+	return client == IPA_CLIENT_Q6_LAN_PROD ||
+		client == IPA_CLIENT_Q6_WAN_PROD ||
+		client == IPA_CLIENT_Q6_CMD_PROD;
+}
+
+static inline bool ipa_ap_consumer(enum ipa_client_type client)
+{
+	return client == IPA_CLIENT_APPS_LAN_CONS ||
+		client == IPA_CLIENT_APPS_WAN_CONS;
+}
+
+/**
+ * enum ipa_irq_type - IPA Interrupt Type
+ *
+ * Used to register handlers for IPA interrupts.
+ */
+enum ipa_irq_type {
+	IPA_INVALID_IRQ = 0,
+	IPA_UC_IRQ_0,
+	IPA_UC_IRQ_1,
+	IPA_TX_SUSPEND_IRQ,
+	IPA_IRQ_MAX
+};
+
+/**
+ * typedef ipa_irq_handler_t - irq handler/callback type
+ * @param ipa_irq_type		- interrupt type
+ * @param interrupt_data	- interrupt information data
+ *
+ * Callback function registered by ipa_add_interrupt_handler() to
+ * handle a specific interrupt type
+ */
+typedef void (*ipa_irq_handler_t)(enum ipa_irq_type interrupt,
+				  u32 interrupt_data);
+
+/**
+ * struct ipa_tx_suspend_irq_data - Interrupt data for IPA_TX_SUSPEND_IRQ
+ * @endpoints:	Bitmask of endpoints which cause IPA_TX_SUSPEND_IRQ interrupt
+ */
+struct ipa_tx_suspend_irq_data {
+	u32 endpoints;
+};
+
+/**
+ * enum ipa_dp_evt_type - Data path event type
+ */
+enum ipa_dp_evt_type {
+	IPA_RECEIVE,
+	IPA_WRITE_DONE,
+	IPA_CLIENT_START_POLL,
+	IPA_CLIENT_COMP_NAPI,
+};
+
+typedef void (*ipa_notify_cb)(void *priv, enum ipa_dp_evt_type evt,
+			      unsigned long data);
+
+/**
+ * struct ipa_ep_context - IPA end point context
+ * @allocated:	True when the endpoint has been allocated
+ * @client:	Client associated with the endpoint
+ * @channel_id:	EP's GSI channel
+ * @evt_ring_id: EP's GSI channel event ring
+ * @priv:	Pointer supplied when client_notify is called
+ *	  notified for new data avail
+ * @client_notify: Function called for event notification
+ * @napi_enabled: Endpoint uses NAPI
+ */
+struct ipa_ep_context {
+	bool allocated;
+	enum ipa_client_type client;
+	u32 channel_id;
+	u32 evt_ring_id;
+	bool bytes_xfered_valid;
+	u16 bytes_xfered;
+
+	struct ipa_reg_endp_init_hdr init_hdr;
+	struct ipa_reg_endp_init_hdr_ext hdr_ext;
+	struct ipa_reg_endp_init_mode init_mode;
+	struct ipa_reg_endp_init_aggr init_aggr;
+	struct ipa_reg_endp_init_cfg init_cfg;
+	struct ipa_reg_endp_init_seq init_seq;
+	struct ipa_reg_endp_init_deaggr init_deaggr;
+	struct ipa_reg_endp_init_hdr_metadata_mask metadata_mask;
+	struct ipa_reg_endp_status status;
+
+	void (*client_notify)(void *priv, enum ipa_dp_evt_type evt,
+			      unsigned long data);
+	void *priv;
+	bool napi_enabled;
+	struct ipa_sys_context *sys;
+};
+
+/**
+ * enum ipa_desc_type - IPA decriptor type
+ */
+enum ipa_desc_type {
+	IPA_DATA_DESC,
+	IPA_DATA_DESC_SKB,
+	IPA_DATA_DESC_SKB_PAGED,
+	IPA_IMM_CMD_DESC,
+};
+
+/**
+ * struct ipa_desc - IPA descriptor
+ * @type:	Type of data in the descriptor
+ * @len_opcode: Length of the payload, or opcode for immediate commands
+ * @payload:	Points to descriptor payload (e.g., socket buffer)
+ * @callback:	Completion callback
+ * @user1:	Pointer data supplied to callback
+ * @user2:	Integer data supplied with callback
+ */
+struct ipa_desc {
+	enum ipa_desc_type type;
+	u16 len_opcode;
+	void *payload;
+	void (*callback)(void *user1, int user2);
+	void *user1;
+	int user2;
+};
+
+/**
+ * enum ipahal_imm_cmd:	IPA immediate commands
+ *
+ * All immediate commands are issued using the APPS_CMD_PROD
+ * endpoint.  The numeric values here are the opcodes for IPA v3.5.1
+ * hardware
+ */
+enum ipahal_imm_cmd {
+	IPA_IMM_CMD_IP_V4_FILTER_INIT		= 3,
+	IPA_IMM_CMD_IP_V6_FILTER_INIT		= 4,
+	IPA_IMM_CMD_IP_V4_ROUTING_INIT		= 7,
+	IPA_IMM_CMD_IP_V6_ROUTING_INIT		= 8,
+	IPA_IMM_CMD_HDR_INIT_LOCAL		= 9,
+	IPA_IMM_CMD_DMA_TASK_32B_ADDR		= 17,
+	IPA_IMM_CMD_DMA_SHARED_MEM		= 19,
+};
+
+/**
+ * struct ipa_transport_pm - Transport power management data
+ * @dec_clients:	?
+ * @transport_pm_mutex:	Mutex to protect the transport_pm functionality.
+ */
+struct ipa_transport_pm {
+	atomic_t dec_clients;
+	struct mutex transport_pm_mutex;	/* XXX comment this */
+};
+
+struct ipa_smp2p_info {
+	struct qcom_smem_state *valid_state;
+	struct qcom_smem_state *enabled_state;
+	unsigned int valid_bit;
+	unsigned int enabled_bit;
+	unsigned int clock_query_irq;
+	unsigned int post_init_irq;
+	bool ipa_clk_on;
+	bool res_sent;
+};
+
+struct ipa_dma_task_info {
+	struct ipa_dma_mem mem;
+	void *payload;
+};
+
+/**
+ * struct ipa_context - IPA context
+ * @filter_bitmap:	End-points supporting filtering bitmap
+ * @ipa_irq:		IRQ number used for IPA
+ * @ipa_phys:		Physical address of IPA register memory
+ * @gsi:		Pointer to GSI structure
+ * @dev:		IPA device structure
+ * @ep:			Endpoint array
+ * @dp:			Data path information
+ * @smem_size:		Size of shared memory
+ * @smem_offset:	Offset of the usable area in shared memory
+ * @active_clients_mutex: Used when active clients count changes from/to 0
+ * @active_clients_count: Active client count
+ * @power_mgmt_wq:	Workqueue for power management
+ * @transport_pm:	Transport power management related information
+ * @cmd_prod_ep_id:	Endpoint for APPS_CMD_PROD
+ * @lan_cons_ep_id:	Endpoint for APPS_LAN_CONS
+ * @memory_path:	Path for memory interconnect
+ * @imem_path:		Path for internal memory interconnect
+ * @config_path:	Path for configuration interconnect
+ * @modem_clk_vote_valid: Whether proxy clock vote is held for modem
+ * @ep_count:		Number of endpoints available in hardware
+ * @uc_ctx:		Microcontroller context
+ * @wakeup_lock:	Lock protecting updates to wakeup_count
+ * @wakeup_count:	Count of times wakelock is acquired
+ * @wakeup:		Wakeup source
+ * @ipa_client_apps_wan_cons_agg_gro: APPS_WAN_CONS generic receive offload
+ * @smp2p_info:		Information related to SMP2P
+ * @dma_task_info:	Preallocated DMA task
+ */
+struct ipa_context {
+	u32 filter_bitmap;
+	u32 ipa_irq;
+	phys_addr_t ipa_phys;
+	struct gsi *gsi;
+	struct device *dev;
+
+	struct ipa_ep_context ep[IPA_EP_COUNT_MAX];
+	struct ipa_dp *dp;
+	u32 smem_size;
+	u16 smem_offset;
+	struct mutex active_clients_mutex;	/* count changes from/to 0 */
+	atomic_t active_clients_count;
+	struct workqueue_struct *power_mgmt_wq;
+	struct ipa_transport_pm transport_pm;
+	u32 cmd_prod_ep_id;
+	u32 lan_cons_ep_id;
+	struct icc_path *memory_path;
+	struct icc_path *imem_path;
+	struct icc_path *config_path;
+	struct clk *core_clock;
+	bool modem_clk_vote_valid;
+	u32 ep_count;
+
+	struct ipa_uc_ctx *uc_ctx;
+
+	spinlock_t wakeup_lock;		/* protects updates to wakeup_count */
+	u32 wakeup_count;
+	struct wakeup_source wakeup;
+
+	/* RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA */
+	bool ipa_client_apps_wan_cons_agg_gro;
+	/* M-release support to know client endpoint */
+	struct ipa_smp2p_info smp2p_info;
+	struct ipa_dma_task_info dma_task_info;
+};
+
+extern struct ipa_context *ipa_ctx;
+
+int ipa_wwan_init(void);
+void ipa_wwan_cleanup(void);
+
+int ipa_stop_gsi_channel(u32 ep_id);
+
+void ipa_cfg_ep(u32 ep_id);
+
+int ipa_tx_dp(enum ipa_client_type dst, struct sk_buff *skb);
+
+bool ipa_endp_aggr_support(u32 ep_id);
+enum ipa_seq_type ipa_endp_seq_type(u32 ep_id);
+
+void ipa_endp_init_hdr_cons(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset);
+void ipa_endp_init_hdr_prod(u32 ep_id, u32 header_size,
+			    u32 metadata_offset, u32 length_offset);
+void ipa_endp_init_hdr_ext_cons(u32 ep_id, u32 pad_align,
+				bool pad_included);
+void ipa_endp_init_hdr_ext_prod(u32 ep_id, u32 pad_align);
+void ipa_endp_init_mode_cons(u32 ep_id);
+void ipa_endp_init_mode_prod(u32 ep_id, enum ipa_mode mode,
+			     enum ipa_client_type dst_client);
+void ipa_endp_init_aggr_cons(u32 ep_id, u32 size, u32 count,
+			     bool close_on_eof);
+void ipa_endp_init_aggr_prod(u32 ep_id, enum ipa_aggr_en aggr_en,
+			     enum ipa_aggr_type aggr_type);
+void ipa_endp_init_cfg_cons(u32 ep_id,
+			    enum ipa_cs_offload_en offload_type);
+void ipa_endp_init_cfg_prod(u32 ep_id, enum ipa_cs_offload_en offload_type,
+			    u32 metadata_offset);
+void ipa_endp_init_seq_cons(u32 ep_id);
+void ipa_endp_init_seq_prod(u32 ep_id);
+void ipa_endp_init_deaggr_cons(u32 ep_id);
+void ipa_endp_init_deaggr_prod(u32 ep_id);
+void ipa_endp_init_hdr_metadata_mask_cons(u32 ep_id, u32 mask);
+void ipa_endp_init_hdr_metadata_mask_prod(u32 ep_id);
+void ipa_endp_status_cons(u32 ep_id, bool enable);
+void ipa_endp_status_prod(u32 ep_id, bool enable,
+			  enum ipa_client_type client);
+int ipa_ep_alloc(enum ipa_client_type client);
+void ipa_ep_free(u32 ep_id);
+
+void ipa_no_intr_init(u32 prod_ep_id);
+
+int ipa_ep_setup(u32 ep_id, u32 channel_count, u32 evt_ring_mult,
+		 u32 rx_buffer_size,
+		 void (*client_notify)(void *priv, enum ipa_dp_evt_type type,
+				       unsigned long data),
+		 void *priv);
+
+void ipa_ep_teardown(u32 ep_id);
+
+void ipa_rx_switch_to_poll_mode(struct ipa_sys_context *sys);
+
+void ipa_add_interrupt_handler(enum ipa_irq_type interrupt,
+			       ipa_irq_handler_t handler);
+
+void ipa_remove_interrupt_handler(enum ipa_irq_type interrupt);
+
+void ipa_proxy_clk_vote(void);
+void ipa_proxy_clk_unvote(void);
+
+u32 ipa_filter_bitmap_init(void);
+
+bool ipa_is_modem_ep(u32 ep_id);
+
+u32 ipa_client_ep_id(enum ipa_client_type client);
+u32 ipa_client_channel_id(enum ipa_client_type client);
+u32 ipa_client_tlv_count(enum ipa_client_type client);
+
+void ipa_init_hw(void);
+
+int ipa_interconnect_init(struct device *dev);
+void ipa_interconnect_exit(void);
+
+int ipa_interconnect_enable(void);
+int ipa_interconnect_disable(void);
+
+int ipa_send_cmd_timeout(struct ipa_desc *desc, u32 timeout);
+static inline int ipa_send_cmd(struct ipa_desc *desc)
+{
+	return ipa_send_cmd_timeout(desc, 0);
+}
+
+void ipa_client_add(void);
+bool ipa_client_add_additional(void);
+void ipa_client_remove(void);
+
+u32 ipa_aggr_byte_limit_buf_size(u32 byte_limit);
+
+void ipa_cfg_default_route(enum ipa_client_type client);
+
+int ipa_interrupts_init(void);
+
+void ipa_suspend_active_aggr_wa(u32 ep_id);
+void ipa_lan_rx_cb(void *priv, enum ipa_dp_evt_type evt, unsigned long data);
+
+void ipa_sram_settings_read(void);
+
+int ipa_modem_smem_init(void);
+
+struct ipa_uc_ctx *ipa_uc_init(phys_addr_t phys_addr);
+bool ipa_uc_loaded(void);
+void ipa_uc_panic_notifier(void);
+
+u32 ipa_get_ep_count(void);
+int ipa_ap_suspend(struct device *dev);
+int ipa_ap_resume(struct device *dev);
+void ipa_set_resource_groups_min_max_limits(void);
+void ipa_ep_suspend_all(void);
+void ipa_ep_resume_all(void);
+void ipa_inc_acquire_wakelock(void);
+void ipa_dec_release_wakelock(void);
+int ipa_rx_poll(u32 ep_id, int budget);
+void ipa_reset_freeze_vote(void);
+void ipa_enable_dcd(void);
+
+int ipa_gsi_dma_task_alloc(void);
+void ipa_gsi_dma_task_free(void);
+
+void ipa_set_flt_tuple_mask(u32 ep_id);
+void ipa_set_rt_tuple_mask(int tbl_idx);
+
+void ipa_gsi_irq_rx_notify_cb(void *chan_data, u16 count);
+void ipa_gsi_irq_tx_notify_cb(void *xfer_data);
+
+bool ipa_ep_polling(struct ipa_ep_context *ep);
+
+struct ipa_dp *ipa_dp_init(void);
+void ipa_dp_exit(struct ipa_dp *dp);
+
+#endif /* _IPA_I_H_ */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
  2018-11-07  0:32   ` Alex Elder
  (?)
@ 2018-11-07  0:40     ` Randy Dunlap
  -1 siblings, 0 replies; 76+ messages in thread
From: Randy Dunlap @ 2018-11-07  0:40 UTC (permalink / raw)
  To: Alex Elder, davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: mark.rutland, devicetree, syadagir, netdev, linux-kernel,
	robh+dt, linux-arm-msm, mjavid, linux-soc, linux-arm-kernel

Hi-

On 11/6/18 4:32 PM, Alex Elder wrote:
> diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
> new file mode 100644
> index 000000000000..f8ea9363f532
> --- /dev/null
> +++ b/drivers/net/ipa/Kconfig
> @@ -0,0 +1,30 @@
> +config IPA
> +	tristate "Qualcomm IPA support"
> +	depends on NET
> +	select QCOM_QMI_HELPERS
> +	select QCOM_MDT_LOADER
> +	default n
> +	help
> +	  Choose Y here to include support for the Qualcomm IP
> +	  Accelerator (IPA), a hardware block present in some
> +	  Qualcomm SoCs.  The IPA is a programmable protocol
> +	  processor that is capable of generic hardware handling
> +	  of IP packets, including routing, filtering, and NAT.
> +	  Currently the IPA driver supports only basic transport
> +	  of network traffic between the AP and modem, on the
> +	  Qualcomm SDM845 SoC.
> +
> +	  If unsure, say N.
> +
> +config IPA_ASSERT
> +	bool "Enable IPA assertions"
> +	depends on IPA
> +	default y
> +	help
> +	 Incorporate IPA assertion verification in the build.  This
> +	 cause various design assumptions to be checked at runtime,

	 causes

> +	 generating a report (and a crash) if any assumed condition
> +	 does not hold.  You may wish to disable this to avoid the
> +	 overhead of checking.
> +
> +	 If unsure doubt, say "Y" here.


-- 
~Randy

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
@ 2018-11-07  0:40     ` Randy Dunlap
  0 siblings, 0 replies; 76+ messages in thread
From: Randy Dunlap @ 2018-11-07  0:40 UTC (permalink / raw)
  To: Alex Elder, davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

Hi-

On 11/6/18 4:32 PM, Alex Elder wrote:
> diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
> new file mode 100644
> index 000000000000..f8ea9363f532
> --- /dev/null
> +++ b/drivers/net/ipa/Kconfig
> @@ -0,0 +1,30 @@
> +config IPA
> +	tristate "Qualcomm IPA support"
> +	depends on NET
> +	select QCOM_QMI_HELPERS
> +	select QCOM_MDT_LOADER
> +	default n
> +	help
> +	  Choose Y here to include support for the Qualcomm IP
> +	  Accelerator (IPA), a hardware block present in some
> +	  Qualcomm SoCs.  The IPA is a programmable protocol
> +	  processor that is capable of generic hardware handling
> +	  of IP packets, including routing, filtering, and NAT.
> +	  Currently the IPA driver supports only basic transport
> +	  of network traffic between the AP and modem, on the
> +	  Qualcomm SDM845 SoC.
> +
> +	  If unsure, say N.
> +
> +config IPA_ASSERT
> +	bool "Enable IPA assertions"
> +	depends on IPA
> +	default y
> +	help
> +	 Incorporate IPA assertion verification in the build.  This
> +	 cause various design assumptions to be checked at runtime,

	 causes

> +	 generating a report (and a crash) if any assumed condition
> +	 does not hold.  You may wish to disable this to avoid the
> +	 overhead of checking.
> +
> +	 If unsure doubt, say "Y" here.


-- 
~Randy

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
@ 2018-11-07  0:40     ` Randy Dunlap
  0 siblings, 0 replies; 76+ messages in thread
From: Randy Dunlap @ 2018-11-07  0:40 UTC (permalink / raw)
  To: linux-arm-kernel

Hi-

On 11/6/18 4:32 PM, Alex Elder wrote:
> diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
> new file mode 100644
> index 000000000000..f8ea9363f532
> --- /dev/null
> +++ b/drivers/net/ipa/Kconfig
> @@ -0,0 +1,30 @@
> +config IPA
> +	tristate "Qualcomm IPA support"
> +	depends on NET
> +	select QCOM_QMI_HELPERS
> +	select QCOM_MDT_LOADER
> +	default n
> +	help
> +	  Choose Y here to include support for the Qualcomm IP
> +	  Accelerator (IPA), a hardware block present in some
> +	  Qualcomm SoCs.  The IPA is a programmable protocol
> +	  processor that is capable of generic hardware handling
> +	  of IP packets, including routing, filtering, and NAT.
> +	  Currently the IPA driver supports only basic transport
> +	  of network traffic between the AP and modem, on the
> +	  Qualcomm SDM845 SoC.
> +
> +	  If unsure, say N.
> +
> +config IPA_ASSERT
> +	bool "Enable IPA assertions"
> +	depends on IPA
> +	default y
> +	help
> +	 Incorporate IPA assertion verification in the build.  This
> +	 cause various design assumptions to be checked at runtime,

	 causes

> +	 generating a report (and a crash) if any assumed condition
> +	 does not hold.  You may wish to disable this to avoid the
> +	 overhead of checking.
> +
> +	 If unsure doubt, say "Y" here.


-- 
~Randy

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 11:50     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 11:50 UTC (permalink / raw)
  To: Alex Elder
  Cc: Rob Herring, Mark Rutland, David Miller, Bjorn Andersson,
	Ilias Apalodimas, Networking, DTML, linux-arm-msm, linux-soc,
	Linux ARM, Linux Kernel Mailing List, syadagir, mjavid

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> device tree nodes.
>
> Signed-off-by: Alex Elder <elder@linaro.org>
> ---
>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>  2 files changed, 151 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt

I think this should go into bindings/net instead of bindings/soc, since it's
mostly about networking rather than a specific detail of managing the SoC
itself.

> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> new file mode 100644
> index 000000000000..d4d3d37df029
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> @@ -0,0 +1,136 @@
> +Qualcomm IPA (IP Accelerator) Driver
> +
> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> +the main processor.  The IPA currently serves only as a network interface,
> +providing access to an LTE network available via a modem.

That doesn't belong into the binding. Say what the hardware can do here,
not what a specific implementation of the driver does at this moment.
The binding should be written in an OS independent way after all.

> +- interrupts-extended:
> +       Specifies the IRQs used by the IPA.  Four cells are required,
> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
> +       from the modem; and the "ready for stage 2 initialization"
> +       interrupt from the modem.  The first two are hardware IRQs; the
> +       third and fourth are SMP2P input interrupts.

You mean 'four interrupts', not 'four cells' -- each interrupt specifier
already consists of at least two cells (one for the phandle to the
irqchip, plus one or more cells to describe that interrupt).

> +- interconnects:
> +       Specifies the interconnects used by the IPA.  Three cells are
> +       required, specifying:  the path from the IPA to memory; from
> +       IPA to internal (SoC resident) memory; and between the AP
> +       subsystem and IPA for register access.

Same here and in the rest.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-07 11:50     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 11:50 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> device tree nodes.
>
> Signed-off-by: Alex Elder <elder@linaro.org>
> ---
>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>  2 files changed, 151 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt

I think this should go into bindings/net instead of bindings/soc, since it's
mostly about networking rather than a specific detail of managing the SoC
itself.

> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> new file mode 100644
> index 000000000000..d4d3d37df029
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> @@ -0,0 +1,136 @@
> +Qualcomm IPA (IP Accelerator) Driver
> +
> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> +the main processor.  The IPA currently serves only as a network interface,
> +providing access to an LTE network available via a modem.

That doesn't belong into the binding. Say what the hardware can do here,
not what a specific implementation of the driver does at this moment.
The binding should be written in an OS independent way after all.

> +- interrupts-extended:
> +       Specifies the IRQs used by the IPA.  Four cells are required,
> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
> +       from the modem; and the "ready for stage 2 initialization"
> +       interrupt from the modem.  The first two are hardware IRQs; the
> +       third and fourth are SMP2P input interrupts.

You mean 'four interrupts', not 'four cells' -- each interrupt specifier
already consists of at least two cells (one for the phandle to the
irqchip, plus one or more cells to describe that interrupt).

> +- interconnects:
> +       Specifies the interconnects used by the IPA.  Three cells are
> +       required, specifying:  the path from the IPA to memory; from
> +       IPA to internal (SoC resident) memory; and between the AP
> +       subsystem and IPA for register access.

Same here and in the rest.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 12:17     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 12:17 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> This patch includes code implementing the IPA DMA module, which
> defines a structure to represent a DMA allocation for the IPA device.
> It's used throughout the IPA code.
>
> Signed-off-by: Alex Elder <elder@linaro.org>

I looked through all the users of this and couldn't fine one that actually
benefits from it. I'd say better drop this patch entirely and open-code
the contents in the callers. That will help readability since the dma
API is well understood by many people.

Generally speaking, try not to wrap Linux interfaces into driver specific
helper functions.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers
@ 2018-11-07 12:17     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 12:17 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> This patch includes code implementing the IPA DMA module, which
> defines a structure to represent a DMA allocation for the IPA device.
> It's used throughout the IPA code.
>
> Signed-off-by: Alex Elder <elder@linaro.org>

I looked through all the users of this and couldn't fine one that actually
benefits from it. I'd say better drop this patch entirely and open-code
the contents in the callers. That will help readability since the dma
API is well understood by many people.

Generally speaking, try not to wrap Linux interfaces into driver specific
helper functions.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 12:34     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 12:34 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> +config IPA_ASSERT
> +       bool "Enable IPA assertions"
> +       depends on IPA
> +       default y
> +       help
> +        Incorporate IPA assertion verification in the build.  This
> +        cause various design assumptions to be checked at runtime,
> +        generating a report (and a crash) if any assumed condition
> +        does not hold.  You may wish to disable this to avoid the
> +        overhead of checking.

Maybe remove this from the submission.

> +#define ipa_debug(fmt, args...)        dev_dbg(ipa_ctx->dev, fmt, ## args)
> +#define ipa_err(fmt, args...)  dev_err(ipa_ctx->dev, fmt, ## args)

These macros refer to variables in the caller that are not passed as arguments,
which is generally a bad idea. They also trivially wrap a standard kernel
interface, so better just that directly.

> +#define ipa_bug() \
> +       do {                                                            \
> +               ipa_err("an unrecoverable error has occurred\n");       \
> +               BUG();                                                  \
> +       } while (0)
> +
> +#define ipa_bug_on(condition)                                          \
> +       do {                                                            \
> +               if (condition) {                                \
> +                       ipa_err("ipa_bug_on(%s) failed!\n", #condition); \
> +                       ipa_bug();                                      \
> +               }                                                       \
> +       } while (0)

According to a discussion at the kernel summit, we should generally
try to avoid BUG() as it rarely does anything useful: it crashes the
current task, but in a network driver that usually means killing the
entire kernel since you are not in process context.

Try questioning each one to see if it can possibly happen, or if the
code can be rewritten in a way to guarantee that it cannot.

If continuing after the bug was detected does not cause a security
hole or permanent data corruption, you can also use WARN_ON()
or WARN_ONCE() (without a wrapper).

> +int ipa_wwan_init(void);
> +void ipa_wwan_cleanup(void);
> +
> +int ipa_stop_gsi_channel(u32 ep_id);
> +
> +void ipa_cfg_ep(u32 ep_id);
> +
> +int ipa_tx_dp(enum ipa_client_type dst, struct sk_buff *skb);
> +
> +bool ipa_endp_aggr_support(u32 ep_id);
> +enum ipa_seq_type ipa_endp_seq_type(u32 ep_id);
> +
> +void ipa_endp_init_hdr_cons(u32 ep_id, u32 header_size,
> +                           u32 metadata_offset, u32 length_offset);
> +void ipa_endp_init_hdr_prod(u32 ep_id, u32 header_size,
> +                           u32 metadata_offset, u32 length_offset);

I'm surprised to see many functions that don't take a pointer
to an instance as the first argument, which often indicates
that you have global state variables and the driver won't
work with multiple hardware instances.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
@ 2018-11-07 12:34     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 12:34 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> +config IPA_ASSERT
> +       bool "Enable IPA assertions"
> +       depends on IPA
> +       default y
> +       help
> +        Incorporate IPA assertion verification in the build.  This
> +        cause various design assumptions to be checked at runtime,
> +        generating a report (and a crash) if any assumed condition
> +        does not hold.  You may wish to disable this to avoid the
> +        overhead of checking.

Maybe remove this from the submission.

> +#define ipa_debug(fmt, args...)        dev_dbg(ipa_ctx->dev, fmt, ## args)
> +#define ipa_err(fmt, args...)  dev_err(ipa_ctx->dev, fmt, ## args)

These macros refer to variables in the caller that are not passed as arguments,
which is generally a bad idea. They also trivially wrap a standard kernel
interface, so better just that directly.

> +#define ipa_bug() \
> +       do {                                                            \
> +               ipa_err("an unrecoverable error has occurred\n");       \
> +               BUG();                                                  \
> +       } while (0)
> +
> +#define ipa_bug_on(condition)                                          \
> +       do {                                                            \
> +               if (condition) {                                \
> +                       ipa_err("ipa_bug_on(%s) failed!\n", #condition); \
> +                       ipa_bug();                                      \
> +               }                                                       \
> +       } while (0)

According to a discussion at the kernel summit, we should generally
try to avoid BUG() as it rarely does anything useful: it crashes the
current task, but in a network driver that usually means killing the
entire kernel since you are not in process context.

Try questioning each one to see if it can possibly happen, or if the
code can be rewritten in a way to guarantee that it cannot.

If continuing after the bug was detected does not cause a security
hole or permanent data corruption, you can also use WARN_ON()
or WARN_ONCE() (without a wrapper).

> +int ipa_wwan_init(void);
> +void ipa_wwan_cleanup(void);
> +
> +int ipa_stop_gsi_channel(u32 ep_id);
> +
> +void ipa_cfg_ep(u32 ep_id);
> +
> +int ipa_tx_dp(enum ipa_client_type dst, struct sk_buff *skb);
> +
> +bool ipa_endp_aggr_support(u32 ep_id);
> +enum ipa_seq_type ipa_endp_seq_type(u32 ep_id);
> +
> +void ipa_endp_init_hdr_cons(u32 ep_id, u32 header_size,
> +                           u32 metadata_offset, u32 length_offset);
> +void ipa_endp_init_hdr_prod(u32 ep_id, u32 header_size,
> +                           u32 metadata_offset, u32 length_offset);

I'm surprised to see many functions that don't take a pointer
to an instance as the first argument, which often indicates
that you have global state variables and the driver won't
work with multiple hardware instances.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 13:30     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 13:30 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:

> Note:  This portion of the driver will be heavily affected by
> planned rework on the data path code.

Ok. I don't think the ioctl interface has a real chance of getting merged
into the kernel. You should generally not require any custom user space
tools for a driver like this.

> diff --git a/drivers/net/ipa/msm_rmnet.h b/drivers/net/ipa/msm_rmnet.h
> new file mode 100644
> index 000000000000..042380fd53fb
> --- /dev/null
> +++ b/drivers/net/ipa/msm_rmnet.h

Just for the record: if we really wanted to define ioctls, this would go
into 'include/linux/uapi/msm_rmnet.h' and get installed into the
/usr/include hierarchy on all machines.

> +
> +#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet protocol  */
> +#define RMNET_IOCTL_SET_LLP_IP      0x000089f2 /* Set RAWIP protocol     */
> +#define RMNET_IOCTL_GET_LLP         0x000089f3 /* Get link protocol      */
> +#define RMNET_IOCTL_SET_QOS_ENABLE   0x000089f4 /* Set QoS header enabled */
> +#define RMNET_IOCTL_SET_QOS_DISABLE  0x000089f5 /* Set QoS header disabled*/
> +#define RMNET_IOCTL_GET_QOS         0x000089f6 /* Get QoS header state   */
> +#define RMNET_IOCTL_GET_OPMODE      0x000089f7 /* Get operation mode     */

And the commands would be defined using _IOC/_IOR/_IOW macros that
document which arguments they take


> +#define RMNET_IOCTL_OPEN            0x000089f8 /* Open transport port    */
> +#define RMNET_IOCTL_CLOSE           0x000089f9 /* Close transport port   */
> +#define RMNET_IOCTL_FLOW_ENABLE             0x000089fa /* Flow enable            */
> +#define RMNET_IOCTL_FLOW_DISABLE     0x000089fb /* Flow disable                  */
> +#define RMNET_IOCTL_FLOW_SET_HNDL    0x000089fc /* Set flow handle       */
> +#define RMNET_IOCTL_EXTENDED        0x000089fd /* Extended IOCTLs        */

'extended' interfaces are obviously out of the question entirely, those
would all need to be separate commands.


> +/* User space may not have this defined. */
> +#ifndef IFNAMSIZ
> +#define IFNAMSIZ 16
> +#endif

This is in <linux/if.h>

> +struct rmnet_ioctl_extended_s {
> +       u32     extended_ioctl;
> +       union {

And unions in the ioctl interfaces also wouldn't work.

> +static bool initialized;       /* Avoid duplicate initialization */
> +
> +static struct rmnet_ipa_context rmnet_ipa_ctx_struct;
> +static struct rmnet_ipa_context *rmnet_ipa_ctx = &rmnet_ipa_ctx_struct;

Global variables like these should be removed.

> +/** ipa_wwan_xmit() - Transmits an skb.
> + *
> + * @skb: skb to be transmitted
> + * @dev: network device
> + *
> + * Return codes:
> + * NETDEV_TX_OK: Success
> + * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later
> + */
> +static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> +       struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
> +       unsigned int skb_len;
> +       int outstanding;
> +
> +       if (skb->protocol != htons(ETH_P_MAP)) {
> +               dev_kfree_skb_any(skb);
> +               dev->stats.tx_dropped++;
> +               return NETDEV_TX_OK;
> +       }
> +
> +       /* Control packets are sent even if queue is stopped.  We
> +        * always honor the data and control high-water marks.
> +        */
> +       outstanding = atomic_read(&wwan_ptr->outstanding_pkts);
> +       if (!RMNET_MAP_GET_CD_BIT(skb)) {       /* Data packet? */
> +               if (netif_queue_stopped(dev))
> +                       return NETDEV_TX_BUSY;
> +               if (outstanding >= wwan_ptr->outstanding_high)
> +                       return NETDEV_TX_BUSY;
> +       } else if (outstanding >= wwan_ptr->outstanding_high_ctl) {
> +               return NETDEV_TX_BUSY;
> +       }

This seems to be a poor reimplementation of BQL. Better
use netdev_sent_queue() and netdev_completed_queue()
to do the same thing better.

> +/** apps_ipa_packet_receive_notify() - Rx notify
> + *
> + * @priv: driver context
> + * @evt: event type
> + * @data: data provided with event
> + *
> + * IPA will pass a packet to the Linux network stack with skb->data
> + */
> +static void apps_ipa_packet_receive_notify(void *priv, enum ipa_dp_evt_type evt,
> +                                          unsigned long data)
> +{
> +       struct ipa_wwan_private *wwan_ptr;
> +       struct net_device *dev = priv;
> +
> +       wwan_ptr = netdev_priv(dev);
> +       if (evt == IPA_RECEIVE) {
> +               struct sk_buff *skb = (struct sk_buff *)data;
> +               int ret;
> +               unsigned int packet_len = skb->len;
> +
> +               skb->dev = rmnet_ipa_ctx->dev;
> +               skb->protocol = htons(ETH_P_MAP);
> +
> +               ret = netif_receive_skb(skb);
> +               if (ret) {
> +                       pr_err_ratelimited("fail on netif_receive_skb\n");
> +                       dev->stats.rx_dropped++;
> +               }
> +               dev->stats.rx_packets++;
> +               dev->stats.rx_bytes += packet_len;
> +       } else if (evt == IPA_CLIENT_START_POLL) {
> +               napi_schedule(&wwan_ptr->napi);
> +       } else if (evt == IPA_CLIENT_COMP_NAPI) {
> +               napi_complete(&wwan_ptr->napi);
> +       } else {
> +               ipa_err("Invalid evt %d received in wan_ipa_receive\n", evt);
> +       }
> +}

I don't understand the logic here. Why is this a callback function?
You normally want the data path to be as fast as possible, and the
indirection seems like it would get in the way of that.

Since the function doesn't do much interesting work, could
it be moved into the caller?

> +/** handle_ingress_format() - Ingress data format configuration */
> +static int handle_ingress_format(struct net_device *dev,
> +                                struct rmnet_ioctl_extended_s *in)
> +{

Can you describe how this would be called from user space?
I.e. what is the reason we have to configure anything here?


> +
> +       /* Unsupported requests */
> +       case RMNET_IOCTL_SET_MRU:                       /* Set MRU */
> +       case RMNET_IOCTL_GET_MRU:                       /* Get MRU */
> +       case RMNET_IOCTL_GET_AGGREGATION_COUNT:         /* Get agg count */
> +       case RMNET_IOCTL_SET_AGGREGATION_COUNT:         /* Set agg count */
> +       case RMNET_IOCTL_GET_AGGREGATION_SIZE:          /* Get agg size */
> +       case RMNET_IOCTL_SET_AGGREGATION_SIZE:          /* Set agg size */
> +       case RMNET_IOCTL_FLOW_CONTROL:                  /* Do flow control */
> +       case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:      /* For legacy use */
> +       case RMNET_IOCTL_GET_HWSW_MAP:                  /* Get HW/SW map */
> +       case RMNET_IOCTL_SET_RX_HEADROOM:               /* Set RX Headroom */
> +       case RMNET_IOCTL_SET_QOS_VERSION:               /* Set 8/6 byte QoS */
> +       case RMNET_IOCTL_GET_QOS_VERSION:               /* Get 8/6 byte QoS */
> +       case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:       /* Get QoS modes */
> +       case RMNET_IOCTL_SET_SLEEP_STATE:               /* Set sleep state */
> +       case RMNET_IOCTL_SET_XLAT_DEV_INFO:             /* xlat dev name */
> +       case RMNET_IOCTL_DEREGISTER_DEV:                /* Deregister netdev */
> +               return -ENOTSUPP;       /* Defined, but unsupported command */
> +
> +       default:
> +               return -EINVAL;         /* Invalid (unrecognized) command */
> +       }
> +
> +copy_out:
> +       return copy_to_user(data, &edata, size) ? -EFAULT : 0;
> +}
> +
> +/** ipa_wwan_ioctl() - I/O control for wwan network driver */
> +static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
> +{
> +       struct rmnet_ioctl_data_s ioctl_data = { };
> +       void __user *data;
> +       size_t size;
> +
> +       data = ifr->ifr_ifru.ifru_data;
> +       size = sizeof(ioctl_data);
> +
> +       switch (cmd) {
> +       /* These features are implied; alternatives are not supported */
> +       case RMNET_IOCTL_SET_LLP_IP:            /* RAW IP protocol */
> +       case RMNET_IOCTL_SET_QOS_DISABLE:       /* QoS header disabled */
> +               return 0;
> +
> +       /* These features are not supported; use alternatives */
> +       case RMNET_IOCTL_SET_LLP_ETHERNET:      /* Ethernet protocol */
> +       case RMNET_IOCTL_SET_QOS_ENABLE:        /* QoS header enabled */
> +       case RMNET_IOCTL_GET_OPMODE:            /* Get operation mode */
> +       case RMNET_IOCTL_FLOW_ENABLE:           /* Flow enable */
> +       case RMNET_IOCTL_FLOW_DISABLE:          /* Flow disable */
> +       case RMNET_IOCTL_FLOW_SET_HNDL:         /* Set flow handle */
> +               return -ENOTSUPP;
> +
> +       case RMNET_IOCTL_GET_LLP:               /* Get link protocol */
> +               ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
> +               goto copy_out;
> +
> +       case RMNET_IOCTL_GET_QOS:               /* Get QoS header state */
> +               ioctl_data.u.operation_mode = RMNET_MODE_NONE;
> +               goto copy_out;
> +
> +       case RMNET_IOCTL_OPEN:                  /* Open transport port */
> +       case RMNET_IOCTL_CLOSE:                 /* Close transport port */
> +               return 0;
> +
> +       case RMNET_IOCTL_EXTENDED:              /* Extended IOCTLs */
> +               return ipa_wwan_ioctl_extended(dev, data);
> +
> +       default:
> +               return -EINVAL;
> +       }

It would help to remove everything that is a nop or not implemented
or that returns a constant value here, those are clearly not
relevant for the submission here.

> +
> +static const struct of_device_id rmnet_ipa_dt_match[] = {
> +       {.compatible = "qcom,rmnet-ipa"},
> +       {},
> +};

The match string looks overly generic, surely there must be plans
to have future versions of this that might require identification.

     Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface
@ 2018-11-07 13:30     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 13:30 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:

> Note:  This portion of the driver will be heavily affected by
> planned rework on the data path code.

Ok. I don't think the ioctl interface has a real chance of getting merged
into the kernel. You should generally not require any custom user space
tools for a driver like this.

> diff --git a/drivers/net/ipa/msm_rmnet.h b/drivers/net/ipa/msm_rmnet.h
> new file mode 100644
> index 000000000000..042380fd53fb
> --- /dev/null
> +++ b/drivers/net/ipa/msm_rmnet.h

Just for the record: if we really wanted to define ioctls, this would go
into 'include/linux/uapi/msm_rmnet.h' and get installed into the
/usr/include hierarchy on all machines.

> +
> +#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet protocol  */
> +#define RMNET_IOCTL_SET_LLP_IP      0x000089f2 /* Set RAWIP protocol     */
> +#define RMNET_IOCTL_GET_LLP         0x000089f3 /* Get link protocol      */
> +#define RMNET_IOCTL_SET_QOS_ENABLE   0x000089f4 /* Set QoS header enabled */
> +#define RMNET_IOCTL_SET_QOS_DISABLE  0x000089f5 /* Set QoS header disabled*/
> +#define RMNET_IOCTL_GET_QOS         0x000089f6 /* Get QoS header state   */
> +#define RMNET_IOCTL_GET_OPMODE      0x000089f7 /* Get operation mode     */

And the commands would be defined using _IOC/_IOR/_IOW macros that
document which arguments they take


> +#define RMNET_IOCTL_OPEN            0x000089f8 /* Open transport port    */
> +#define RMNET_IOCTL_CLOSE           0x000089f9 /* Close transport port   */
> +#define RMNET_IOCTL_FLOW_ENABLE             0x000089fa /* Flow enable            */
> +#define RMNET_IOCTL_FLOW_DISABLE     0x000089fb /* Flow disable                  */
> +#define RMNET_IOCTL_FLOW_SET_HNDL    0x000089fc /* Set flow handle       */
> +#define RMNET_IOCTL_EXTENDED        0x000089fd /* Extended IOCTLs        */

'extended' interfaces are obviously out of the question entirely, those
would all need to be separate commands.


> +/* User space may not have this defined. */
> +#ifndef IFNAMSIZ
> +#define IFNAMSIZ 16
> +#endif

This is in <linux/if.h>

> +struct rmnet_ioctl_extended_s {
> +       u32     extended_ioctl;
> +       union {

And unions in the ioctl interfaces also wouldn't work.

> +static bool initialized;       /* Avoid duplicate initialization */
> +
> +static struct rmnet_ipa_context rmnet_ipa_ctx_struct;
> +static struct rmnet_ipa_context *rmnet_ipa_ctx = &rmnet_ipa_ctx_struct;

Global variables like these should be removed.

> +/** ipa_wwan_xmit() - Transmits an skb.
> + *
> + * @skb: skb to be transmitted
> + * @dev: network device
> + *
> + * Return codes:
> + * NETDEV_TX_OK: Success
> + * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later
> + */
> +static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> +       struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
> +       unsigned int skb_len;
> +       int outstanding;
> +
> +       if (skb->protocol != htons(ETH_P_MAP)) {
> +               dev_kfree_skb_any(skb);
> +               dev->stats.tx_dropped++;
> +               return NETDEV_TX_OK;
> +       }
> +
> +       /* Control packets are sent even if queue is stopped.  We
> +        * always honor the data and control high-water marks.
> +        */
> +       outstanding = atomic_read(&wwan_ptr->outstanding_pkts);
> +       if (!RMNET_MAP_GET_CD_BIT(skb)) {       /* Data packet? */
> +               if (netif_queue_stopped(dev))
> +                       return NETDEV_TX_BUSY;
> +               if (outstanding >= wwan_ptr->outstanding_high)
> +                       return NETDEV_TX_BUSY;
> +       } else if (outstanding >= wwan_ptr->outstanding_high_ctl) {
> +               return NETDEV_TX_BUSY;
> +       }

This seems to be a poor reimplementation of BQL. Better
use netdev_sent_queue() and netdev_completed_queue()
to do the same thing better.

> +/** apps_ipa_packet_receive_notify() - Rx notify
> + *
> + * @priv: driver context
> + * @evt: event type
> + * @data: data provided with event
> + *
> + * IPA will pass a packet to the Linux network stack with skb->data
> + */
> +static void apps_ipa_packet_receive_notify(void *priv, enum ipa_dp_evt_type evt,
> +                                          unsigned long data)
> +{
> +       struct ipa_wwan_private *wwan_ptr;
> +       struct net_device *dev = priv;
> +
> +       wwan_ptr = netdev_priv(dev);
> +       if (evt == IPA_RECEIVE) {
> +               struct sk_buff *skb = (struct sk_buff *)data;
> +               int ret;
> +               unsigned int packet_len = skb->len;
> +
> +               skb->dev = rmnet_ipa_ctx->dev;
> +               skb->protocol = htons(ETH_P_MAP);
> +
> +               ret = netif_receive_skb(skb);
> +               if (ret) {
> +                       pr_err_ratelimited("fail on netif_receive_skb\n");
> +                       dev->stats.rx_dropped++;
> +               }
> +               dev->stats.rx_packets++;
> +               dev->stats.rx_bytes += packet_len;
> +       } else if (evt == IPA_CLIENT_START_POLL) {
> +               napi_schedule(&wwan_ptr->napi);
> +       } else if (evt == IPA_CLIENT_COMP_NAPI) {
> +               napi_complete(&wwan_ptr->napi);
> +       } else {
> +               ipa_err("Invalid evt %d received in wan_ipa_receive\n", evt);
> +       }
> +}

I don't understand the logic here. Why is this a callback function?
You normally want the data path to be as fast as possible, and the
indirection seems like it would get in the way of that.

Since the function doesn't do much interesting work, could
it be moved into the caller?

> +/** handle_ingress_format() - Ingress data format configuration */
> +static int handle_ingress_format(struct net_device *dev,
> +                                struct rmnet_ioctl_extended_s *in)
> +{

Can you describe how this would be called from user space?
I.e. what is the reason we have to configure anything here?


> +
> +       /* Unsupported requests */
> +       case RMNET_IOCTL_SET_MRU:                       /* Set MRU */
> +       case RMNET_IOCTL_GET_MRU:                       /* Get MRU */
> +       case RMNET_IOCTL_GET_AGGREGATION_COUNT:         /* Get agg count */
> +       case RMNET_IOCTL_SET_AGGREGATION_COUNT:         /* Set agg count */
> +       case RMNET_IOCTL_GET_AGGREGATION_SIZE:          /* Get agg size */
> +       case RMNET_IOCTL_SET_AGGREGATION_SIZE:          /* Set agg size */
> +       case RMNET_IOCTL_FLOW_CONTROL:                  /* Do flow control */
> +       case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:      /* For legacy use */
> +       case RMNET_IOCTL_GET_HWSW_MAP:                  /* Get HW/SW map */
> +       case RMNET_IOCTL_SET_RX_HEADROOM:               /* Set RX Headroom */
> +       case RMNET_IOCTL_SET_QOS_VERSION:               /* Set 8/6 byte QoS */
> +       case RMNET_IOCTL_GET_QOS_VERSION:               /* Get 8/6 byte QoS */
> +       case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:       /* Get QoS modes */
> +       case RMNET_IOCTL_SET_SLEEP_STATE:               /* Set sleep state */
> +       case RMNET_IOCTL_SET_XLAT_DEV_INFO:             /* xlat dev name */
> +       case RMNET_IOCTL_DEREGISTER_DEV:                /* Deregister netdev */
> +               return -ENOTSUPP;       /* Defined, but unsupported command */
> +
> +       default:
> +               return -EINVAL;         /* Invalid (unrecognized) command */
> +       }
> +
> +copy_out:
> +       return copy_to_user(data, &edata, size) ? -EFAULT : 0;
> +}
> +
> +/** ipa_wwan_ioctl() - I/O control for wwan network driver */
> +static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
> +{
> +       struct rmnet_ioctl_data_s ioctl_data = { };
> +       void __user *data;
> +       size_t size;
> +
> +       data = ifr->ifr_ifru.ifru_data;
> +       size = sizeof(ioctl_data);
> +
> +       switch (cmd) {
> +       /* These features are implied; alternatives are not supported */
> +       case RMNET_IOCTL_SET_LLP_IP:            /* RAW IP protocol */
> +       case RMNET_IOCTL_SET_QOS_DISABLE:       /* QoS header disabled */
> +               return 0;
> +
> +       /* These features are not supported; use alternatives */
> +       case RMNET_IOCTL_SET_LLP_ETHERNET:      /* Ethernet protocol */
> +       case RMNET_IOCTL_SET_QOS_ENABLE:        /* QoS header enabled */
> +       case RMNET_IOCTL_GET_OPMODE:            /* Get operation mode */
> +       case RMNET_IOCTL_FLOW_ENABLE:           /* Flow enable */
> +       case RMNET_IOCTL_FLOW_DISABLE:          /* Flow disable */
> +       case RMNET_IOCTL_FLOW_SET_HNDL:         /* Set flow handle */
> +               return -ENOTSUPP;
> +
> +       case RMNET_IOCTL_GET_LLP:               /* Get link protocol */
> +               ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
> +               goto copy_out;
> +
> +       case RMNET_IOCTL_GET_QOS:               /* Get QoS header state */
> +               ioctl_data.u.operation_mode = RMNET_MODE_NONE;
> +               goto copy_out;
> +
> +       case RMNET_IOCTL_OPEN:                  /* Open transport port */
> +       case RMNET_IOCTL_CLOSE:                 /* Close transport port */
> +               return 0;
> +
> +       case RMNET_IOCTL_EXTENDED:              /* Extended IOCTLs */
> +               return ipa_wwan_ioctl_extended(dev, data);
> +
> +       default:
> +               return -EINVAL;
> +       }

It would help to remove everything that is a nop or not implemented
or that returns a constant value here, those are clearly not
relevant for the submission here.

> +
> +static const struct of_device_id rmnet_ipa_dt_match[] = {
> +       {.compatible = "qcom,rmnet-ipa"},
> +       {},
> +};

The match string looks overly generic, surely there must be plans
to have future versions of this that might require identification.

     Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 14:08     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 14:08 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:

> +static void ipa_client_remove_deferred(struct work_struct *work);

Try to avoid forward declarations by reordering the code in call order,
it will also make it easier to read.

> +static DECLARE_WORK(ipa_client_remove_work, ipa_client_remove_deferred);
> +
> +static struct ipa_context ipa_ctx_struct;
> +struct ipa_context *ipa_ctx = &ipa_ctx_struct;

Global state variables should generally be removed as well, and
passed around as function arguments.

> +static int hdr_init_local_cmd(u32 offset, u32 size)
> +{
> +       struct ipa_desc desc = { };
> +       struct ipa_dma_mem mem;
> +       void *payload;
> +       int ret;
> +
> +       if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
> +               return -ENOMEM;
> +
> +       offset += ipa_ctx->smem_offset;
> +
> +       payload = ipahal_hdr_init_local_pyld(&mem, offset);
> +       if (!payload) {
> +               ret = -ENOMEM;
> +               goto err_dma_free;
> +       }
> +
> +       desc.type = IPA_IMM_CMD_DESC;
> +       desc.len_opcode = IPA_IMM_CMD_HDR_INIT_LOCAL;
> +       desc.payload = payload;
> +
> +       ret = ipa_send_cmd(&desc);

You have a bunch of dynamic allocations in here, which you
then immediately tear down again after the command is complete.
I can't see at all what you do with the DMA address, since you
seem to not use the virtual address at all but only store
the physical address in some kind of descriptor without ever
writing to it.

Am I missing something here?

> +/* Remoteproc callbacks for SSR events: prepare, start, stop, unprepare */
> +int ipa_ssr_prepare(struct rproc_subdev *subdev)
> +{
> +       printk("======== SSR prepare received ========\n");

I think you mean dev_dbg() here. A plain printk() without a level
is not correct and we probably don't want those messages to arrive
on the console for normal users.

> +static int ipa_firmware_load(struct de
> +
> +err_clear_dev:
> +       ipa_ctx->lan_cons_ep_id = 0;
> +       ipa_ctx->cmd_prod_ep_id = 0;
> +       ipahal_exit();
> +err_dma_exit:
> +       ipa_dma_exit();
> +err_clear_gsi:
> +       ipa_ctx->gsi = NULL;
> +       ipa_ctx->ipa_phys = 0;
> +       ipa_reg_exit();
> +err_clear_ipa_irq:
> +       ipa_ctx->ipa_irq = 0;
> +err_clear_filter_bitmap:
> +       ipa_ctx->filter_bitmap = 0;
> +err_interconnect_exit:
> +       ipa_interconnect_exit();
> +err_clock_exit:
> +       ipa_clock_exit();
> +       ipa_ctx->dev = NULL;
> +out_smp2p_exit:
> +       ipa_smp2p_exit(dev);
> +

No need to initialize members to zero when you are about
to free the structure.

> +static struct platform_driver ipa_plat_drv = {
> +       .probe = ipa_plat_drv_probe,
> +       .remove = ipa_plat_drv_remove,
> +       .driver = {
> +               .name = "ipa",
> +               .owner = THIS_MODULE,
> +               .pm = &ipa_pm_ops,
> +               .of_match_table = ipa_plat_drv_match,
> +       },
> +};
> +
> +builtin_platform_driver(ipa_plat_drv);

This should be module_platform_driver(), and allow unloading
the driver.

        Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file
@ 2018-11-07 14:08     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 14:08 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:

> +static void ipa_client_remove_deferred(struct work_struct *work);

Try to avoid forward declarations by reordering the code in call order,
it will also make it easier to read.

> +static DECLARE_WORK(ipa_client_remove_work, ipa_client_remove_deferred);
> +
> +static struct ipa_context ipa_ctx_struct;
> +struct ipa_context *ipa_ctx = &ipa_ctx_struct;

Global state variables should generally be removed as well, and
passed around as function arguments.

> +static int hdr_init_local_cmd(u32 offset, u32 size)
> +{
> +       struct ipa_desc desc = { };
> +       struct ipa_dma_mem mem;
> +       void *payload;
> +       int ret;
> +
> +       if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
> +               return -ENOMEM;
> +
> +       offset += ipa_ctx->smem_offset;
> +
> +       payload = ipahal_hdr_init_local_pyld(&mem, offset);
> +       if (!payload) {
> +               ret = -ENOMEM;
> +               goto err_dma_free;
> +       }
> +
> +       desc.type = IPA_IMM_CMD_DESC;
> +       desc.len_opcode = IPA_IMM_CMD_HDR_INIT_LOCAL;
> +       desc.payload = payload;
> +
> +       ret = ipa_send_cmd(&desc);

You have a bunch of dynamic allocations in here, which you
then immediately tear down again after the command is complete.
I can't see at all what you do with the DMA address, since you
seem to not use the virtual address at all but only store
the physical address in some kind of descriptor without ever
writing to it.

Am I missing something here?

> +/* Remoteproc callbacks for SSR events: prepare, start, stop, unprepare */
> +int ipa_ssr_prepare(struct rproc_subdev *subdev)
> +{
> +       printk("======== SSR prepare received ========\n");

I think you mean dev_dbg() here. A plain printk() without a level
is not correct and we probably don't want those messages to arrive
on the console for normal users.

> +static int ipa_firmware_load(struct de
> +
> +err_clear_dev:
> +       ipa_ctx->lan_cons_ep_id = 0;
> +       ipa_ctx->cmd_prod_ep_id = 0;
> +       ipahal_exit();
> +err_dma_exit:
> +       ipa_dma_exit();
> +err_clear_gsi:
> +       ipa_ctx->gsi = NULL;
> +       ipa_ctx->ipa_phys = 0;
> +       ipa_reg_exit();
> +err_clear_ipa_irq:
> +       ipa_ctx->ipa_irq = 0;
> +err_clear_filter_bitmap:
> +       ipa_ctx->filter_bitmap = 0;
> +err_interconnect_exit:
> +       ipa_interconnect_exit();
> +err_clock_exit:
> +       ipa_clock_exit();
> +       ipa_ctx->dev = NULL;
> +out_smp2p_exit:
> +       ipa_smp2p_exit(dev);
> +

No need to initialize members to zero when you are about
to free the structure.

> +static struct platform_driver ipa_plat_drv = {
> +       .probe = ipa_plat_drv_probe,
> +       .remove = ipa_plat_drv_remove,
> +       .driver = {
> +               .name = "ipa",
> +               .owner = THIS_MODULE,
> +               .pm = &ipa_pm_ops,
> +               .of_match_table = ipa_plat_drv_match,
> +       },
> +};
> +
> +builtin_platform_driver(ipa_plat_drv);

This should be module_platform_driver(), and allow unloading
the driver.

        Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 04/12] soc: qcom: ipa: immediate commands
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 14:36     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 14:36 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> +/**
> + * struct ipahal_context - HAL global context data
> + * @empty_fltrt_tbl:   Empty table to be used for table initialization
> + */
> +static struct ipahal_context {
> +       struct ipa_dma_mem empty_fltrt_tbl;
> +} ipahal_ctx_struct;
> +static struct ipahal_context *ipahal_ctx = &ipahal_ctx_struct;

Remove the global variables here

> +/* Immediate commands H/W structures */
> +
> +/* struct ipa_imm_cmd_hw_ip_fltrt_init - IP_V*_FILTER_INIT/IP_V*_ROUTING_INIT
> + * command payload in H/W format.
> + * Inits IPv4/v6 routing or filter block.
> + * @hash_rules_addr: Addr in system mem where hashable flt/rt rules starts
> + * @hash_rules_size: Size in bytes of the hashable tbl to cpy to local mem
> + * @hash_local_addr: Addr in shared mem where hashable flt/rt tbl should
> + *  be copied to
> + * @nhash_rules_size: Size in bytes of the non-hashable tbl to cpy to local mem
> + * @nhash_local_addr: Addr in shared mem where non-hashable flt/rt tbl should
> + *  be copied to
> + * @rsvd: reserved
> + * @nhash_rules_addr: Addr in sys mem where non-hashable flt/rt tbl starts
> + */
> +struct ipa_imm_cmd_hw_ip_fltrt_init {
> +       u64 hash_rules_addr;
> +       u64 hash_rules_size     : 12,
> +           hash_local_addr     : 16,
> +           nhash_rules_size    : 12,
> +           nhash_local_addr    : 16,
> +           rsvd                : 8;
> +       u64 nhash_rules_addr;
> +};

In hardware structures, you should not use bit fields, as the ordering
of the bits is not well-defined in C. The only portable way to do this
is to use shifts and masks unfortunately.

> +struct ipa_imm_cmd_hw_hdr_init_local {
> +       u64 hdr_table_addr;
> +       u32 size_hdr_table      : 12,
> +           hdr_addr            : 16,
> +           rsvd                : 4;
> +};

I would also add a 'u32 pad' member at the end to make the padding
explicit here, or mark the first member as '__aligned(4) __packed'
if you want to avoid the padding.

> +void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset)
> +{
> +       struct ipa_imm_cmd_hw_dma_shared_mem *data;
> +
> +       ipa_assert(mem->size < 1 << 16);        /* size is 16 bits wide */
> +       ipa_assert(offset < 1 << 16);           /* local_addr is 16 bits wide */
> +
> +       data = kzalloc(sizeof(*data), GFP_KERNEL);
> +       if (!data)
> +               return NULL;
> +
> +       data->size = mem->size;
> +       data->local_addr = offset;
> +       data->direction = 0;    /* 0 = write to IPA; 1 = read from IPA */
> +       data->skip_pipeline_clear = 0;
> +       data->pipeline_clear_options = IPAHAL_HPS_CLEAR;
> +       data->system_addr = mem->phys;
> +
> +       return data;
> +}

The 'void *' return looks odd here, and also the dynamic allocation.
It looks to me like all these functions could be better done the
other way round, basically putting the
ipa_imm_cmd_hw_dma_shared_mem etc structures on the stack
of the caller. At least for this one, the dynamic allocation
doesn't help at all because the caller is the same that
frees it again after the command. I suspect the same is
true for a lot of those commands.

       Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 04/12] soc: qcom: ipa: immediate commands
@ 2018-11-07 14:36     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 14:36 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> +/**
> + * struct ipahal_context - HAL global context data
> + * @empty_fltrt_tbl:   Empty table to be used for table initialization
> + */
> +static struct ipahal_context {
> +       struct ipa_dma_mem empty_fltrt_tbl;
> +} ipahal_ctx_struct;
> +static struct ipahal_context *ipahal_ctx = &ipahal_ctx_struct;

Remove the global variables here

> +/* Immediate commands H/W structures */
> +
> +/* struct ipa_imm_cmd_hw_ip_fltrt_init - IP_V*_FILTER_INIT/IP_V*_ROUTING_INIT
> + * command payload in H/W format.
> + * Inits IPv4/v6 routing or filter block.
> + * @hash_rules_addr: Addr in system mem where hashable flt/rt rules starts
> + * @hash_rules_size: Size in bytes of the hashable tbl to cpy to local mem
> + * @hash_local_addr: Addr in shared mem where hashable flt/rt tbl should
> + *  be copied to
> + * @nhash_rules_size: Size in bytes of the non-hashable tbl to cpy to local mem
> + * @nhash_local_addr: Addr in shared mem where non-hashable flt/rt tbl should
> + *  be copied to
> + * @rsvd: reserved
> + * @nhash_rules_addr: Addr in sys mem where non-hashable flt/rt tbl starts
> + */
> +struct ipa_imm_cmd_hw_ip_fltrt_init {
> +       u64 hash_rules_addr;
> +       u64 hash_rules_size     : 12,
> +           hash_local_addr     : 16,
> +           nhash_rules_size    : 12,
> +           nhash_local_addr    : 16,
> +           rsvd                : 8;
> +       u64 nhash_rules_addr;
> +};

In hardware structures, you should not use bit fields, as the ordering
of the bits is not well-defined in C. The only portable way to do this
is to use shifts and masks unfortunately.

> +struct ipa_imm_cmd_hw_hdr_init_local {
> +       u64 hdr_table_addr;
> +       u32 size_hdr_table      : 12,
> +           hdr_addr            : 16,
> +           rsvd                : 4;
> +};

I would also add a 'u32 pad' member at the end to make the padding
explicit here, or mark the first member as '__aligned(4) __packed'
if you want to avoid the padding.

> +void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset)
> +{
> +       struct ipa_imm_cmd_hw_dma_shared_mem *data;
> +
> +       ipa_assert(mem->size < 1 << 16);        /* size is 16 bits wide */
> +       ipa_assert(offset < 1 << 16);           /* local_addr is 16 bits wide */
> +
> +       data = kzalloc(sizeof(*data), GFP_KERNEL);
> +       if (!data)
> +               return NULL;
> +
> +       data->size = mem->size;
> +       data->local_addr = offset;
> +       data->direction = 0;    /* 0 = write to IPA; 1 = read from IPA */
> +       data->skip_pipeline_clear = 0;
> +       data->pipeline_clear_options = IPAHAL_HPS_CLEAR;
> +       data->system_addr = mem->phys;
> +
> +       return data;
> +}

The 'void *' return looks odd here, and also the dynamic allocation.
It looks to me like all these functions could be better done the
other way round, basically putting the
ipa_imm_cmd_hw_dma_shared_mem etc structures on the stack
of the caller. At least for this one, the dynamic allocation
doesn't help at all because the caller is the same that
frees it again after the command. I suspect the same is
true for a lot of those commands.

       Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 10/12] soc: qcom: ipa: data path
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 14:55     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 14:55 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> This patch contains "ipa_dp.c", which includes the bulk of the data
> path code.  There is an overview in the code of how things operate,
> but there are already plans to rework this portion of the driver.
>
> In particular:
>   - Interrupt handling will be replaced with a threaded interrupt
>     handler.  Currently handling occurs in a combination of
>     interrupt and workqueue context, and this requires locking
>     and atomic operations for proper synchronization.

You probably don't want to use just a threaded IRQ handler to
start the poll function, that would still require an extra indirection.

However, you can probably use the top half of the threaded
handler to request the poll function if necessary but use
the bottom half for anything that does not go through poll.

>   - Currently, only receive endpoints use NAPI.  Transmit
>     completion interrupts are disabled, and are handled in batches
>     by periodically scheduling an interrupting no-op request.
>     The plan is to arrange for transmit requests to generate
>     interrupts, and their completion will be processed with other
>     completions in the NAPI poll function.  This will also allow
>     accurate feedback about packet sojourn time to be provided to
>     queue limiting mechanisms.

Right, that is definitely required here. I also had a look at
the gsi_channel_queue() function, which sits in the middle of
the transmit function and is rather unoptimized. I'd suggest moving
that into the caller so we can see what is going on, and then
optimizing it from there.

>   - Not all receive endpoints use NAPI.  The plan is for *all*
>     endpoints to use NAPI.  And because all endpoints share a
>     common GSI interrupt, a single NAPI structure will used to
>     managing the processing for all completions on all endpoints.
>   - Receive buffers are posted to the hardware by a workqueue
>     function.  Instead, the plan is to have this done by the
>     NAPI poll routine.

Makes sense, yes.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 10/12] soc: qcom: ipa: data path
@ 2018-11-07 14:55     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 14:55 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>
> This patch contains "ipa_dp.c", which includes the bulk of the data
> path code.  There is an overview in the code of how things operate,
> but there are already plans to rework this portion of the driver.
>
> In particular:
>   - Interrupt handling will be replaced with a threaded interrupt
>     handler.  Currently handling occurs in a combination of
>     interrupt and workqueue context, and this requires locking
>     and atomic operations for proper synchronization.

You probably don't want to use just a threaded IRQ handler to
start the poll function, that would still require an extra indirection.

However, you can probably use the top half of the threaded
handler to request the poll function if necessary but use
the bottom half for anything that does not go through poll.

>   - Currently, only receive endpoints use NAPI.  Transmit
>     completion interrupts are disabled, and are handled in batches
>     by periodically scheduling an interrupting no-op request.
>     The plan is to arrange for transmit requests to generate
>     interrupts, and their completion will be processed with other
>     completions in the NAPI poll function.  This will also allow
>     accurate feedback about packet sojourn time to be provided to
>     queue limiting mechanisms.

Right, that is definitely required here. I also had a look at
the gsi_channel_queue() function, which sits in the middle of
the transmit function and is rather unoptimized. I'd suggest moving
that into the caller so we can see what is going on, and then
optimizing it from there.

>   - Not all receive endpoints use NAPI.  The plan is for *all*
>     endpoints to use NAPI.  And because all endpoints share a
>     common GSI interrupt, a single NAPI structure will used to
>     managing the processing for all completions on all endpoints.
>   - Receive buffers are posted to the hardware by a workqueue
>     function.  Instead, the plan is to have this done by the
>     NAPI poll routine.

Makes sense, yes.

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 14:59     ` Rob Herring
  -1 siblings, 0 replies; 76+ messages in thread
From: Rob Herring @ 2018-11-07 14:59 UTC (permalink / raw)
  To: Alex Elder
  Cc: Rob Herring, Mark Rutland, davem, Arnd Bergmann, Bjorn Andersson,
	ilias.apalodimas, netdev, devicetree, linux-arm-msm, linux-soc,
	linux-arm-kernel, Linux Kernel Mailing List, syadagir, mjavid

On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
>
> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> device tree nodes.
>
> Signed-off-by: Alex Elder <elder@linaro.org>
> ---
>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>  2 files changed, 151 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>
> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> new file mode 100644
> index 000000000000..d4d3d37df029
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> @@ -0,0 +1,136 @@
> +Qualcomm IPA (IP Accelerator) Driver

Bindings are for h/w not drivers.

> +
> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> +the main processor.  The IPA currently serves only as a network interface,
> +providing access to an LTE network available via a modem.
> +
> +The IPA sits between multiple independent "execution environments,"
> +including the AP subsystem (APSS) and the modem.  The IPA presents
> +a Generic Software Interface (GSI) to each execution environment.
> +The GSI is an integral part of the IPA, but it is logically isolated
> +and has a distinct interrupt and a separately-defined address space.
> +
> +    ----------   -------------   ---------
> +    |        |   |G|       |G|   |       |
> +    |  APSS  |===|S|  IPA  |S|===| Modem |
> +    |        |   |I|       |I|   |       |
> +    ----------   -------------   ---------
> +
> +See also:
> +  bindings/interrupt-controller/interrupts.txt
> +  bindings/interconnect/interconnect.txt
> +  bindings/soc/qcom/qcom,smp2p.txt
> +  bindings/reserved-memory/reserved-memory.txt
> +  bindings/clock/clock-bindings.txt
> +
> +All properties defined below are required.
> +
> +- compatible:
> +       Must be one of the following compatible strings:
> +               "qcom,ipa-sdm845-modem_init"
> +               "qcom,ipa-sdm845-tz_init"

Normal order is <vendor>,<soc>-<ipblock>.

Don't use '_'.

What's the difference between these 2? It can't be detected somehow?
This might be better expressed as a property. Then if Trustzone
initializes things, it can just add a property.

> +
> +-reg:
> +       Resources specyfing the physical address spaces of the IPA and GSI.

typo

> +
> +-reg-names:
> +       The names of the address space ranges defined by the "reg" property.
> +       Must be "ipa" and "gsi".
> +
> +- interrupts-extended:

Use 'interrupts' here and describe what they are and the order. What
they are connected to (and the need for interrupts-extended) is
outside the scope of this binding.

> +       Specifies the IRQs used by the IPA.  Four cells are required,
> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
> +       from the modem; and the "ready for stage 2 initialization"
> +       interrupt from the modem.  The first two are hardware IRQs; the
> +       third and fourth are SMP2P input interrupts.
> +
> +- interrupt-names:
> +       The names of the interrupts defined by the "interrupts-extended"
> +       property.  Must be "ipa", "gsi", "ipa-clock-query", and
> +       "ipa-post-init".

Format as one per line.

> +
> +- clocks:
> +       Resource that defines the IPA core clock.
> +
> +- clock-names:
> +       The name used for the IPA core clock.  Must be "core".
> +
> +- interconnects:
> +       Specifies the interconnects used by the IPA.  Three cells are
> +       required, specifying:  the path from the IPA to memory; from
> +       IPA to internal (SoC resident) memory; and between the AP
> +       subsystem and IPA for register access.
> +
> +- interconnect-names:
> +       The names of the interconnects defined by the "interconnects"
> +       property.  Must be "memory", "imem", and "config".
> +
> +- qcom,smem-states
> +       The state bits used for SMP2P output.  Two cells must be specified.
> +       The first indicates whether the value in the second bit is valid
> +       (1 means valid).  The second, if valid, defines whether the IPA
> +       clock is enabled (1 means enabled).
> +
> +- qcom,smem-state-names
> +       The names of the state bits used for SMP2P output.  These must be
> +       "ipa-clock-enabled-valid" and "ipa-clock-enabled".
> +
> +- memory-region
> +       A phandle for a reserved memory area that holds the firmware passed
> +       to Trust Zone for authentication.  (Note, this is required
> +       only for "qcom,ipa-sdm845-tz_init".)
> +
> += EXAMPLE
> +
> +The following example represents the IPA present in the SDM845 SoC.  It
> +shows portions of the "modem-smp2p" node to indicate its relationship
> +with the interrupts and SMEM states used by the IPA.
> +
> +       modem-smp2p {
> +               compatible = "qcom,smp2p";
> +               . . .
> +               ipa_smp2p_out: ipa-ap-to-modem {
> +                       qcom,entry-name = "ipa";
> +                       #qcom,smem-state-cells = <1>;
> +               };
> +
> +               ipa_smp2p_in: ipa-modem-to-ap {
> +                       qcom,entry-name = "ipa";
> +                       interrupt-controller;
> +                       #interrupt-cells = <2>;
> +               };
> +       };
> +
> +       ipa@1e00000 {

ipa@1e40000

> +               compatible = "qcom,ipa-sdm845-modem_init";
> +
> +               reg = <0x1e40000 0x34000>,
> +                     <0x1e04000 0x2c000>;
> +               reg-names = "ipa",
> +                           "gsi";
> +
> +               interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
> +                                     <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
> +                                     <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
> +                                     <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
> +               interrupt-names = "ipa",
> +                                 "gsi",
> +                                 "ipa-clock-query",
> +                                 "ipa-post-init";
> +
> +               clocks = <&rpmhcc RPMH_IPA_CLK>;
> +               clock-names = "core";
> +
> +               interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
> +                               <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
> +                               <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
> +               interconnect-names = "memory",
> +                                    "imem",
> +                                    "config";
> +
> +               qcom,smem-states = <&ipa_smp2p_out 0>,
> +                                  <&ipa_smp2p_out 1>;
> +               qcom,smem-state-names = "ipa-clock-enabled-valid",
> +                                       "ipa-clock-enabled";
> +       };
> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> new file mode 100644
> index 000000000000..3d0b2aabefc7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> @@ -0,0 +1,15 @@
> +Qualcomm IPA RMNet Driver
> +
> +This binding describes the IPA RMNet driver, which is used to
> +represent virtual interfaces available on the modem accessed via
> +the IPA.  Other than the compatible string there are no properties
> +associated with this device.

Only a compatible string is a sure sign this is not a h/w device and
you are just abusing DT to instantiate drivers. Make the IPA driver
instantiate any sub drivers it needs.

Rob

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-07 14:59     ` Rob Herring
  0 siblings, 0 replies; 76+ messages in thread
From: Rob Herring @ 2018-11-07 14:59 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
>
> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> device tree nodes.
>
> Signed-off-by: Alex Elder <elder@linaro.org>
> ---
>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>  2 files changed, 151 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>
> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> new file mode 100644
> index 000000000000..d4d3d37df029
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> @@ -0,0 +1,136 @@
> +Qualcomm IPA (IP Accelerator) Driver

Bindings are for h/w not drivers.

> +
> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> +the main processor.  The IPA currently serves only as a network interface,
> +providing access to an LTE network available via a modem.
> +
> +The IPA sits between multiple independent "execution environments,"
> +including the AP subsystem (APSS) and the modem.  The IPA presents
> +a Generic Software Interface (GSI) to each execution environment.
> +The GSI is an integral part of the IPA, but it is logically isolated
> +and has a distinct interrupt and a separately-defined address space.
> +
> +    ----------   -------------   ---------
> +    |        |   |G|       |G|   |       |
> +    |  APSS  |===|S|  IPA  |S|===| Modem |
> +    |        |   |I|       |I|   |       |
> +    ----------   -------------   ---------
> +
> +See also:
> +  bindings/interrupt-controller/interrupts.txt
> +  bindings/interconnect/interconnect.txt
> +  bindings/soc/qcom/qcom,smp2p.txt
> +  bindings/reserved-memory/reserved-memory.txt
> +  bindings/clock/clock-bindings.txt
> +
> +All properties defined below are required.
> +
> +- compatible:
> +       Must be one of the following compatible strings:
> +               "qcom,ipa-sdm845-modem_init"
> +               "qcom,ipa-sdm845-tz_init"

Normal order is <vendor>,<soc>-<ipblock>.

Don't use '_'.

What's the difference between these 2? It can't be detected somehow?
This might be better expressed as a property. Then if Trustzone
initializes things, it can just add a property.

> +
> +-reg:
> +       Resources specyfing the physical address spaces of the IPA and GSI.

typo

> +
> +-reg-names:
> +       The names of the address space ranges defined by the "reg" property.
> +       Must be "ipa" and "gsi".
> +
> +- interrupts-extended:

Use 'interrupts' here and describe what they are and the order. What
they are connected to (and the need for interrupts-extended) is
outside the scope of this binding.

> +       Specifies the IRQs used by the IPA.  Four cells are required,
> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
> +       from the modem; and the "ready for stage 2 initialization"
> +       interrupt from the modem.  The first two are hardware IRQs; the
> +       third and fourth are SMP2P input interrupts.
> +
> +- interrupt-names:
> +       The names of the interrupts defined by the "interrupts-extended"
> +       property.  Must be "ipa", "gsi", "ipa-clock-query", and
> +       "ipa-post-init".

Format as one per line.

> +
> +- clocks:
> +       Resource that defines the IPA core clock.
> +
> +- clock-names:
> +       The name used for the IPA core clock.  Must be "core".
> +
> +- interconnects:
> +       Specifies the interconnects used by the IPA.  Three cells are
> +       required, specifying:  the path from the IPA to memory; from
> +       IPA to internal (SoC resident) memory; and between the AP
> +       subsystem and IPA for register access.
> +
> +- interconnect-names:
> +       The names of the interconnects defined by the "interconnects"
> +       property.  Must be "memory", "imem", and "config".
> +
> +- qcom,smem-states
> +       The state bits used for SMP2P output.  Two cells must be specified.
> +       The first indicates whether the value in the second bit is valid
> +       (1 means valid).  The second, if valid, defines whether the IPA
> +       clock is enabled (1 means enabled).
> +
> +- qcom,smem-state-names
> +       The names of the state bits used for SMP2P output.  These must be
> +       "ipa-clock-enabled-valid" and "ipa-clock-enabled".
> +
> +- memory-region
> +       A phandle for a reserved memory area that holds the firmware passed
> +       to Trust Zone for authentication.  (Note, this is required
> +       only for "qcom,ipa-sdm845-tz_init".)
> +
> += EXAMPLE
> +
> +The following example represents the IPA present in the SDM845 SoC.  It
> +shows portions of the "modem-smp2p" node to indicate its relationship
> +with the interrupts and SMEM states used by the IPA.
> +
> +       modem-smp2p {
> +               compatible = "qcom,smp2p";
> +               . . .
> +               ipa_smp2p_out: ipa-ap-to-modem {
> +                       qcom,entry-name = "ipa";
> +                       #qcom,smem-state-cells = <1>;
> +               };
> +
> +               ipa_smp2p_in: ipa-modem-to-ap {
> +                       qcom,entry-name = "ipa";
> +                       interrupt-controller;
> +                       #interrupt-cells = <2>;
> +               };
> +       };
> +
> +       ipa at 1e00000 {

ipa at 1e40000

> +               compatible = "qcom,ipa-sdm845-modem_init";
> +
> +               reg = <0x1e40000 0x34000>,
> +                     <0x1e04000 0x2c000>;
> +               reg-names = "ipa",
> +                           "gsi";
> +
> +               interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
> +                                     <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
> +                                     <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
> +                                     <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
> +               interrupt-names = "ipa",
> +                                 "gsi",
> +                                 "ipa-clock-query",
> +                                 "ipa-post-init";
> +
> +               clocks = <&rpmhcc RPMH_IPA_CLK>;
> +               clock-names = "core";
> +
> +               interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
> +                               <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
> +                               <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
> +               interconnect-names = "memory",
> +                                    "imem",
> +                                    "config";
> +
> +               qcom,smem-states = <&ipa_smp2p_out 0>,
> +                                  <&ipa_smp2p_out 1>;
> +               qcom,smem-state-names = "ipa-clock-enabled-valid",
> +                                       "ipa-clock-enabled";
> +       };
> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> new file mode 100644
> index 000000000000..3d0b2aabefc7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> @@ -0,0 +1,15 @@
> +Qualcomm IPA RMNet Driver
> +
> +This binding describes the IPA RMNet driver, which is used to
> +represent virtual interfaces available on the modem accessed via
> +the IPA.  Other than the compatible string there are no properties
> +associated with this device.

Only a compatible string is a sure sign this is not a h/w device and
you are just abusing DT to instantiate drivers. Make the IPA driver
instantiate any sub drivers it needs.

Rob

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 15:00     ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 15:00 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
> new file mode 100644
> index 000000000000..5e0aa6163235
> --- /dev/null
> +++ b/drivers/net/ipa/ipa_reg.c
> @@ -0,0 +1,972 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +
> +#include <linux/types.h>
> +#include <linux/io.h>
> +#include <linux/bitfield.h>
> +
> +#include "ipa_reg.h"
> +
> +/* I/O remapped base address of IPA register space */
> +static void __iomem *ipa_reg_virt;

This should of course be part of the device structure.

> +/* struct ipa_reg_desc - descriptor for an abstracted hardware register
> + *
> + * @construct - fn to construct the register value from its field structure
> + * @parse - function to parse register field values into its field structure
> + * @offset - register offset relative to base address
> + * @n_ofst - size multiplier for "N-parameterized" registers
> + */
> +struct ipa_reg_desc {
> +       u32 (*construct)(enum ipa_reg reg, const void *fields);
> +       void (*parse)(enum ipa_reg reg, void *fields, u32 val);
> +       u32 offset;
> +       u16 n_ofst;
> +};

Indirect function pointers can be a bit expensive in the post-spectre
days. It's probably not overly important if these are always part of
an MMIO access function, but you should be careful about using
these in the data path.

How many different versions do we have to support in practice?

       Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
@ 2018-11-07 15:00     ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 15:00 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
> new file mode 100644
> index 000000000000..5e0aa6163235
> --- /dev/null
> +++ b/drivers/net/ipa/ipa_reg.c
> @@ -0,0 +1,972 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +
> +#include <linux/types.h>
> +#include <linux/io.h>
> +#include <linux/bitfield.h>
> +
> +#include "ipa_reg.h"
> +
> +/* I/O remapped base address of IPA register space */
> +static void __iomem *ipa_reg_virt;

This should of course be part of the device structure.

> +/* struct ipa_reg_desc - descriptor for an abstracted hardware register
> + *
> + * @construct - fn to construct the register value from its field structure
> + * @parse - function to parse register field values into its field structure
> + * @offset - register offset relative to base address
> + * @n_ofst - size multiplier for "N-parameterized" registers
> + */
> +struct ipa_reg_desc {
> +       u32 (*construct)(enum ipa_reg reg, const void *fields);
> +       void (*parse)(enum ipa_reg reg, void *fields, u32 val);
> +       u32 offset;
> +       u16 n_ofst;
> +};

Indirect function pointers can be a bit expensive in the post-spectre
days. It's probably not overly important if these are always part of
an MMIO access function, but you should be careful about using
these in the data path.

How many different versions do we have to support in practice?

       Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface
  2018-11-07  0:32   ` Alex Elder
@ 2018-11-07 15:26     ` Dan Williams
  -1 siblings, 0 replies; 76+ messages in thread
From: Dan Williams @ 2018-11-07 15:26 UTC (permalink / raw)
  To: Alex Elder, davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

On Tue, 2018-11-06 at 18:32 -0600, Alex Elder wrote:
> The IPA uses "rmnet" as a way to present remote network resources as
> if they were local to the AP.  IPA interfaces representing networks
> accessible via the modem are represented as rmnet interfaces,
> implemented by the "rmnet data driver" found here:
>     drivers/net/ethernet/qualcomm/rmnet/

It looks like there's a lot of overlap between this driver and that
one.  Ideally they would be a single driver which could automatically
select the IPA mode for appropriate hardware, or the IPA mode would be
a "subdriver" that bases itself heavily on the existing rmnet driver
even if it doesn't use the same packet movement functions.  Or
something like that.

But as Arnd stated, the ioctls won't fly.  They were also proposed for
the rmnet driver but they are better done as netlink, or via sysfs as
the existing qmi_wwan driver does for some of the same values.

Half the non-extended ioctls aren't even supported/used and should
simply be removed.

The extended ioctls should be evaluated as to whether they are really
needed (eg RMNET_IOCTL_GET_DRIVER_NAME).  I think most of the rest have
either been handled already via the 'rmnet' driver itself (like the MUX
channels using the vlan netlink attributes) or should be added via
netlink-type mechansims if they are really required.

In general, it would be good to get to a single 'rmnet' driver that has
a single API and supports as much hardware as possible.  There's too
much duplication currently.

Dan

> The IPA is able to perform aggregation of packets, as well as
> checksum offload.  These options (plus others, such as configuring
> MTU size) are configurable using an ioctl interface.  In addition,
> rmnet devices support multiplexing.
> 
> TX packets are handed to the data path layer, and when their
> transmission is complete the notification callback will be
> called.  The data path code posts RX packets to the hardware,
> and when they are filled they are supplied here by a receive
> notification callback.
> 
> The IPA driver currently does not support the modem shutting down
> (or crashing).  But the rmnet_ipa device roughly represents the
> availability of networks reachable by the modem.  If the modem is
> operational, an ipa_rmnet network device will be available.  Modem
> operation is managed by the remoteproc subsystem.
> 
> Note:  This portion of the driver will be heavily affected by
> planned rework on the data path code.
> 
> Signed-off-by: Alex Elder <elder@linaro.org>
> ---
>  drivers/net/ipa/msm_rmnet.h    | 120 +++++
>  drivers/net/ipa/rmnet_config.h |  31 ++
>  drivers/net/ipa/rmnet_ipa.c    | 805
> +++++++++++++++++++++++++++++++++
>  3 files changed, 956 insertions(+)
>  create mode 100644 drivers/net/ipa/msm_rmnet.h
>  create mode 100644 drivers/net/ipa/rmnet_config.h
>  create mode 100644 drivers/net/ipa/rmnet_ipa.c
> 
> diff --git a/drivers/net/ipa/msm_rmnet.h
> b/drivers/net/ipa/msm_rmnet.h
> new file mode 100644
> index 000000000000..042380fd53fb
> --- /dev/null
> +++ b/drivers/net/ipa/msm_rmnet.h
> @@ -0,0 +1,120 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +#ifndef _MSM_RMNET_H_
> +#define _MSM_RMNET_H_
> +
> +/* Bitmap macros for RmNET driver operation mode. */
> +#define RMNET_MODE_NONE	    0x00
> +#define RMNET_MODE_LLP_ETH  0x01
> +#define RMNET_MODE_LLP_IP   0x02
> +#define RMNET_MODE_QOS	    0x04
> +
> +/* IOCTL commands
> + * Values chosen to not conflict with other drivers in the ecosystem
> + */
> +
> +#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet
> protocol  */
> +#define RMNET_IOCTL_SET_LLP_IP	     0x000089f2 /* Set RAWIP
> protocol	  */
> +#define RMNET_IOCTL_GET_LLP	     0x000089f3 /* Get link
> protocol	  */
> +#define RMNET_IOCTL_SET_QOS_ENABLE   0x000089f4 /* Set QoS header
> enabled */
> +#define RMNET_IOCTL_SET_QOS_DISABLE  0x000089f5 /* Set QoS header
> disabled*/
> +#define RMNET_IOCTL_GET_QOS	     0x000089f6 /* Get QoS header
> state	  */
> +#define RMNET_IOCTL_GET_OPMODE	     0x000089f7 /* Get
> operation mode	  */
> +#define RMNET_IOCTL_OPEN	     0x000089f8 /* Open transport
> port	  */
> +#define RMNET_IOCTL_CLOSE	     0x000089f9 /* Close transport
> port	  */
> +#define RMNET_IOCTL_FLOW_ENABLE	     0x000089fa /* Flow
> enable		  */
> +#define RMNET_IOCTL_FLOW_DISABLE     0x000089fb /* Flow disable	
> 	  */
> +#define RMNET_IOCTL_FLOW_SET_HNDL    0x000089fc /* Set flow handle	
>   */
> +#define RMNET_IOCTL_EXTENDED	     0x000089fd /* Extended
> IOCTLs	  */
> +
> +/* RmNet Data Required IOCTLs */
> +#define RMNET_IOCTL_GET_SUPPORTED_FEATURES     0x0000	/* Get
> features	   */
> +#define RMNET_IOCTL_SET_MRU		       0x0001	/*
> Set MRU	   */
> +#define RMNET_IOCTL_GET_MRU		       0x0002	/*
> Get MRU	   */
> +#define RMNET_IOCTL_GET_EPID		       0x0003	/*
> Get endpoint ID */
> +#define RMNET_IOCTL_GET_DRIVER_NAME	       0x0004	/*
> Get driver name */
> +#define RMNET_IOCTL_ADD_MUX_CHANNEL	       0x0005	/*
> Add MUX ID	   */
> +#define RMNET_IOCTL_SET_EGRESS_DATA_FORMAT     0x0006	/* Set
> EDF	   */
> +#define RMNET_IOCTL_SET_INGRESS_DATA_FORMAT    0x0007	/* Set
> IDF	   */
> +#define RMNET_IOCTL_SET_AGGREGATION_COUNT      0x0008	/* Set
> agg count   */
> +#define RMNET_IOCTL_GET_AGGREGATION_COUNT      0x0009	/* Get
> agg count   */
> +#define RMNET_IOCTL_SET_AGGREGATION_SIZE       0x000a	/* Set
> agg size	   */
> +#define RMNET_IOCTL_GET_AGGREGATION_SIZE       0x000b	/* Get
> agg size	   */
> +#define RMNET_IOCTL_FLOW_CONTROL	       0x000c	/* Do
> flow control */
> +#define RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL   0x000d	/* For
> legacy use  */
> +#define RMNET_IOCTL_GET_HWSW_MAP	       0x000e	/* Get
> HW/SW map   */
> +#define RMNET_IOCTL_SET_RX_HEADROOM	       0x000f	/*
> RX Headroom	   */
> +#define RMNET_IOCTL_GET_EP_PAIR		       0x0010	
> /* Endpoint pair   */
> +#define RMNET_IOCTL_SET_QOS_VERSION	       0x0011	/*
> 8/6 byte QoS hdr*/
> +#define RMNET_IOCTL_GET_QOS_VERSION	       0x0012	/*
> 8/6 byte QoS hdr*/
> +#define RMNET_IOCTL_GET_SUPPORTED_QOS_MODES    0x0013	/* Get
> QoS modes   */
> +#define RMNET_IOCTL_SET_SLEEP_STATE	       0x0014	/*
> Set sleep state */
> +#define RMNET_IOCTL_SET_XLAT_DEV_INFO	       0x0015	/*
> xlat dev name   */
> +#define RMNET_IOCTL_DEREGISTER_DEV	       0x0016	/*
> Dereg a net dev */
> +#define RMNET_IOCTL_GET_SG_SUPPORT	       0x0017	/*
> Query sg support*/
> +
> +/* Return values for the RMNET_IOCTL_GET_SUPPORTED_FEATURES IOCTL */
> +#define RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL		BIT(0)
> +#define RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT		BIT(1
> )
> +#define RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT	BIT(2)
> +
> +/* Input values for the RMNET_IOCTL_SET_EGRESS_DATA_FORMAT IOCTL  */
> +#define RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION		BIT(2)
> +#define RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM		BIT(4)
> +
> +/* Input values for the RMNET_IOCTL_SET_INGRESS_DATA_FORMAT IOCTL */
> +#define RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM		BIT(4)
> +#define RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA		BIT(5)
> +
> +/* User space may not have this defined. */
> +#ifndef IFNAMSIZ
> +#define IFNAMSIZ 16
> +#endif
> +
> +struct rmnet_ioctl_extended_s {
> +	u32	extended_ioctl;
> +	union {
> +		u32	data; /* Generic data field for most
> extended IOCTLs */
> +
> +		/* Return values for
> +		 *    RMNET_IOCTL_GET_DRIVER_NAME
> +		 *    RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL
> +		 */
> +		char	if_name[IFNAMSIZ];
> +
> +		/* Input values for the RMNET_IOCTL_ADD_MUX_CHANNEL
> IOCTL */
> +		struct {
> +			u32	mux_id;
> +			char	vchannel_name[IFNAMSIZ];
> +		} rmnet_mux_val;
> +
> +		/* Input values for the RMNET_IOCTL_FLOW_CONTROL
> IOCTL */
> +		struct {
> +			u8	flow_mode;
> +			u8	mux_id;
> +		} flow_control_prop;
> +
> +		/* Return values for RMNET_IOCTL_GET_EP_PAIR */
> +		struct {
> +			u32	consumer_pipe_num;
> +			u32	producer_pipe_num;
> +		} ipa_ep_pair;
> +
> +		struct {
> +			u32	__data; /* Placeholder for legacy
> data*/
> +			u32	agg_size;
> +			u32	agg_count;
> +		} ingress_format;
> +	} u;
> +};
> +
> +struct rmnet_ioctl_data_s {
> +	union {
> +		u32	operation_mode;
> +		u32	tcm_handle;
> +	} u;
> +};
> +#endif /* _MSM_RMNET_H_ */
> diff --git a/drivers/net/ipa/rmnet_config.h
> b/drivers/net/ipa/rmnet_config.h
> new file mode 100644
> index 000000000000..3b9a549ca1bd
> --- /dev/null
> +++ b/drivers/net/ipa/rmnet_config.h
> @@ -0,0 +1,31 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2016-2018, The Linux Foundation. All rights
> reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +#ifndef _RMNET_CONFIG_H_
> +#define _RMNET_CONFIG_H_
> +
> +#include <linux/types.h>
> +
> +/* XXX We want to use struct rmnet_map_header, but that's currently
> defined in
> + * XXX     drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
> + * XXX We also want to use RMNET_MAP_GET_CD_BIT(Y), defined in the
> same file.
> + */
> +struct rmnet_map_header_s {
> +#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
> +	u8	pad_len		: 6,
> +		reserved_bit	: 1,
> +		cd_bit		: 1;
> +#else
> +	u8	cd_bit		: 1,
> +		reserved_bit	: 1,
> +		pad_len		: 6;
> +#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
> +	u8	mux_id;
> +	u16	pkt_len;
> +}  __aligned(1);
> +
> +#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *)Y-
> >data)->cd_bit)
> +
> +#endif /* _RMNET_CONFIG_H_ */
> diff --git a/drivers/net/ipa/rmnet_ipa.c
> b/drivers/net/ipa/rmnet_ipa.c
> new file mode 100644
> index 000000000000..7006afe3a5ea
> --- /dev/null
> +++ b/drivers/net/ipa/rmnet_ipa.c
> @@ -0,0 +1,805 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2014-2018, The Linux Foundation. All rights
> reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +
> +/* WWAN Transport Network Driver. */
> +
> +#include <linux/completion.h>
> +#include <linux/errno.h>
> +#include <linux/if_arp.h>
> +#include <linux/interrupt.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/of_device.h>
> +#include <linux/string.h>
> +#include <linux/skbuff.h>
> +#include <linux/version.h>
> +#include <linux/workqueue.h>
> +#include <net/pkt_sched.h>
> +
> +#include "msm_rmnet.h"
> +#include "rmnet_config.h"
> +#include "ipa_qmi.h"
> +#include "ipa_i.h"
> +
> +#define DRIVER_NAME		"wwan_ioctl"
> +#define IPA_WWAN_DEV_NAME	"rmnet_ipa%d"
> +
> +#define MUX_CHANNEL_MAX		10	/* max mux channels
> */
> +
> +#define NAPI_WEIGHT		60
> +
> +#define WWAN_DATA_LEN		2000
> +#define HEADROOM_FOR_QMAP	8	/* for mux header */
> +#define TAILROOM		0	/* for padding by mux layer
> */
> +
> +#define DEFAULT_OUTSTANDING_HIGH	128
> +#define DEFAULT_OUTSTANDING_HIGH_CTL	(DEFAULT_OUTSTANDING_HIG
> H + 32)
> +#define DEFAULT_OUTSTANDING_LOW		64
> +
> +#define IPA_APPS_WWAN_CONS_RING_COUNT	256
> +#define IPA_APPS_WWAN_PROD_RING_COUNT	512
> +
> +static int ipa_rmnet_poll(struct napi_struct *napi, int budget);
> +
> +/** struct ipa_wwan_private - WWAN private data
> + * @net: network interface struct implemented by this driver
> + * @stats: iface statistics
> + * @outstanding_high: number of outstanding packets allowed
> + * @outstanding_low: number of outstanding packets which shall cause
> + *
> + * WWAN private - holds all relevant info about WWAN driver
> + */
> +struct ipa_wwan_private {
> +	struct net_device_stats stats;
> +	atomic_t outstanding_pkts;
> +	int outstanding_high_ctl;
> +	int outstanding_high;
> +	int outstanding_low;
> +	struct napi_struct napi;
> +};
> +
> +struct rmnet_ipa_context {
> +	struct net_device *dev;
> +	struct mutex mux_id_mutex;		/* protects
> mux_id[] */
> +	u32 mux_id_count;
> +	u32 mux_id[MUX_CHANNEL_MAX];
> +	u32 wan_prod_ep_id;
> +	u32 wan_cons_ep_id;
> +	struct mutex ep_setup_mutex;		/* endpoint
> setup/teardown */
> +};
> +
> +static bool initialized;	/* Avoid duplicate initialization */
> +
> +static struct rmnet_ipa_context rmnet_ipa_ctx_struct;
> +static struct rmnet_ipa_context *rmnet_ipa_ctx =
> &rmnet_ipa_ctx_struct;
> +
> +/** wwan_open() - Opens the wwan network interface */
> +static int ipa_wwan_open(struct net_device *dev)
> +{
> +	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
> +
> +	napi_enable(&wwan_ptr->napi);
> +	netif_start_queue(dev);
> +
> +	return 0;
> +}
> +
> +/** ipa_wwan_stop() - Stops the wwan network interface. */
> +static int ipa_wwan_stop(struct net_device *dev)
> +{
> +	netif_stop_queue(dev);
> +
> +	return 0;
> +}
> +
> +/** ipa_wwan_xmit() - Transmits an skb.
> + *
> + * @skb: skb to be transmitted
> + * @dev: network device
> + *
> + * Return codes:
> + * NETDEV_TX_OK: Success
> + * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later
> + */
> +static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device
> *dev)
> +{
> +	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
> +	unsigned int skb_len;
> +	int outstanding;
> +
> +	if (skb->protocol != htons(ETH_P_MAP)) {
> +		dev_kfree_skb_any(skb);
> +		dev->stats.tx_dropped++;
> +		return NETDEV_TX_OK;
> +	}
> +
> +	/* Control packets are sent even if queue is stopped.  We
> +	 * always honor the data and control high-water marks.
> +	 */
> +	outstanding = atomic_read(&wwan_ptr->outstanding_pkts);
> +	if (!RMNET_MAP_GET_CD_BIT(skb)) {	/* Data packet? */
> +		if (netif_queue_stopped(dev))
> +			return NETDEV_TX_BUSY;
> +		if (outstanding >= wwan_ptr->outstanding_high)
> +			return NETDEV_TX_BUSY;
> +	} else if (outstanding >= wwan_ptr->outstanding_high_ctl) {
> +		return NETDEV_TX_BUSY;
> +	}
> +
> +	/* both data packets and commands will be routed to
> +	 * IPA_CLIENT_Q6_WAN_CONS based on status configuration.
> +	 */
> +	skb_len = skb->len;
> +	if (ipa_tx_dp(IPA_CLIENT_APPS_WAN_PROD, skb))
> +		return NETDEV_TX_BUSY;
> +
> +	atomic_inc(&wwan_ptr->outstanding_pkts);
> +	dev->stats.tx_packets++;
> +	dev->stats.tx_bytes += skb_len;
> +
> +	return NETDEV_TX_OK;
> +}
> +
> +/** apps_ipa_tx_complete_notify() - Rx notify
> + *
> + * @priv: driver context
> + * @evt: event type
> + * @data: data provided with event
> + *
> + * Check that the packet is the one we sent and release it
> + * This function will be called in defered context in IPA wq.
> + */
> +static void apps_ipa_tx_complete_notify(void *priv, enum
> ipa_dp_evt_type evt,
> +					unsigned long data)
> +{
> +	struct ipa_wwan_private *wwan_ptr;
> +	struct net_device *dev = priv;
> +	struct sk_buff *skb;
> +
> +	skb = (struct sk_buff *)data;
> +
> +	if (dev != rmnet_ipa_ctx->dev) {
> +		dev_kfree_skb_any(skb);
> +		return;
> +	}
> +
> +	if (evt != IPA_WRITE_DONE) {
> +		ipa_err("unsupported evt on Tx callback, Drop the
> packet\n");
> +		dev_kfree_skb_any(skb);
> +		dev->stats.tx_dropped++;
> +		return;
> +	}
> +
> +	wwan_ptr = netdev_priv(dev);
> +	atomic_dec(&wwan_ptr->outstanding_pkts);
> +	__netif_tx_lock_bh(netdev_get_tx_queue(dev, 0));
> +	if (netif_queue_stopped(dev) &&
> +	    atomic_read(&wwan_ptr->outstanding_pkts) <
> +				wwan_ptr->outstanding_low) {
> +		netif_wake_queue(dev);
> +	}
> +
> +	__netif_tx_unlock_bh(netdev_get_tx_queue(dev, 0));
> +	dev_kfree_skb_any(skb);
> +}
> +
> +/** apps_ipa_packet_receive_notify() - Rx notify
> + *
> + * @priv: driver context
> + * @evt: event type
> + * @data: data provided with event
> + *
> + * IPA will pass a packet to the Linux network stack with skb->data
> + */
> +static void apps_ipa_packet_receive_notify(void *priv, enum
> ipa_dp_evt_type evt,
> +					   unsigned long data)
> +{
> +	struct ipa_wwan_private *wwan_ptr;
> +	struct net_device *dev = priv;
> +
> +	wwan_ptr = netdev_priv(dev);
> +	if (evt == IPA_RECEIVE) {
> +		struct sk_buff *skb = (struct sk_buff *)data;
> +		int ret;
> +		unsigned int packet_len = skb->len;
> +
> +		skb->dev = rmnet_ipa_ctx->dev;
> +		skb->protocol = htons(ETH_P_MAP);
> +
> +		ret = netif_receive_skb(skb);
> +		if (ret) {
> +			pr_err_ratelimited("fail on
> netif_receive_skb\n");
> +			dev->stats.rx_dropped++;
> +		}
> +		dev->stats.rx_packets++;
> +		dev->stats.rx_bytes += packet_len;
> +	} else if (evt == IPA_CLIENT_START_POLL) {
> +		napi_schedule(&wwan_ptr->napi);
> +	} else if (evt == IPA_CLIENT_COMP_NAPI) {
> +		napi_complete(&wwan_ptr->napi);
> +	} else {
> +		ipa_err("Invalid evt %d received in
> wan_ipa_receive\n", evt);
> +	}
> +}
> +
> +/** handle_ingress_format() - Ingress data format configuration */
> +static int handle_ingress_format(struct net_device *dev,
> +				 struct rmnet_ioctl_extended_s *in)
> +{
> +	enum ipa_cs_offload_en offload_type;
> +	enum ipa_client_type client;
> +	u32 metadata_offset;
> +	u32 rx_buffer_size;
> +	u32 channel_count;
> +	u32 length_offset;
> +	u32 header_size;
> +	bool aggr_active;
> +	u32 aggr_bytes;
> +	u32 aggr_count;
> +	u32 aggr_size;	/* in KB */
> +	u32 ep_id;
> +	int ret;
> +
> +	client = IPA_CLIENT_APPS_WAN_CONS;
> +	channel_count = IPA_APPS_WWAN_CONS_RING_COUNT;
> +	header_size = sizeof(struct rmnet_map_header_s);
> +	metadata_offset = offsetof(struct rmnet_map_header_s,
> mux_id);
> +	length_offset = offsetof(struct rmnet_map_header_s,
> pkt_len);
> +	offload_type = IPA_CS_OFFLOAD_NONE;
> +	aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT;
> +	aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT;
> +	aggr_active = false;
> +
> +	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM)
> +		offload_type = IPA_CS_OFFLOAD_DL;
> +
> +	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA) {
> +		aggr_bytes = in->u.ingress_format.agg_size;
> +		aggr_count = in->u.ingress_format.agg_count;
> +		aggr_active = true;
> +	}
> +
> +	if (aggr_bytes > ipa_reg_aggr_max_byte_limit())
> +		return -EINVAL;
> +
> +	if (aggr_count > ipa_reg_aggr_max_packet_limit())
> +		return -EINVAL;
> +
> +	/* Compute the buffer size required to handle the requested
> +	 * aggregation byte limit.  The aggr_byte_limit value is
> +	 * expressed as a number of KB, but we derive that value
> +	 * after computing the buffer size to use (in bytes).  The
> +	 * buffer must be sufficient to hold one IPA_MTU-sized
> +	 * packet *after* the limit is reached.
> +	 *
> +	 * (Note that the rx_buffer_size value reflects only the
> +	 * space for data, not any standard metadata or headers.)
> +	 */
> +	rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes);
> +
> +	/* Account for the extra IPA_MTU past the limit in the
> +	 * buffer, and convert the result to the KB units the
> +	 * aggr_byte_limit uses.
> +	 */
> +	aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K;
> +
> +	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
> +		ret = -EBUSY;
> +		goto out_unlock;
> +	}
> +
> +	ret = ipa_ep_alloc(client);
> +	if (ret < 0)
> +		goto out_unlock;
> +	ep_id = ret;
> +
> +	/* Record our endpoint configuration parameters */
> +	ipa_endp_init_hdr_cons(ep_id, header_size, metadata_offset,
> +			       length_offset);
> +	ipa_endp_init_hdr_ext_cons(ep_id, 0, true);
> +	ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, true);
> +	ipa_endp_init_cfg_cons(ep_id, offload_type);
> +	ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0xff000000);
> +	ipa_endp_status_cons(ep_id, !aggr_active);
> +
> +	ipa_ctx->ipa_client_apps_wan_cons_agg_gro = aggr_active;
> +
> +	ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size,
> +			   apps_ipa_packet_receive_notify, dev);
> +	if (ret)
> +		ipa_ep_free(ep_id);
> +	else
> +		rmnet_ipa_ctx->wan_cons_ep_id = ep_id;
> +out_unlock:
> +	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	return ret;
> +}
> +
> +/** handle_egress_format() - Egress data format configuration */
> +static int handle_egress_format(struct net_device *dev,
> +				struct rmnet_ioctl_extended_s *e)
> +{
> +	enum ipa_cs_offload_en offload_type;
> +	enum ipa_client_type dst_client;
> +	enum ipa_client_type client;
> +	enum ipa_aggr_type aggr_type;
> +	enum ipa_aggr_en aggr_en;
> +	u32 channel_count;
> +	u32 length_offset;
> +	u32 header_align;
> +	u32 header_offset;
> +	u32 header_size;
> +	u32 ep_id;
> +	int ret;
> +
> +	client = IPA_CLIENT_APPS_WAN_PROD;
> +	dst_client = IPA_CLIENT_APPS_LAN_CONS;
> +	channel_count = IPA_APPS_WWAN_PROD_RING_COUNT;
> +	header_size = sizeof(struct rmnet_map_header_s);
> +	offload_type = IPA_CS_OFFLOAD_NONE;
> +	aggr_en = IPA_BYPASS_AGGR;
> +	aggr_type = 0;	/* ignored if BYPASS */
> +	header_offset = 0;
> +	length_offset = 0;
> +	header_align = 0;
> +
> +	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM) {
> +		offload_type = IPA_CS_OFFLOAD_UL;
> +		header_offset = sizeof(struct rmnet_map_header_s) /
> 4;
> +		header_size += sizeof(u32);
> +	}
> +
> +	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION) {
> +		aggr_en = IPA_ENABLE_DEAGGR;
> +		aggr_type = IPA_QCMAP;
> +		length_offset = offsetof(struct rmnet_map_header_s,
> pkt_len);
> +		header_align = ilog2(sizeof(u32));
> +	}
> +
> +	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
> +		ret = -EBUSY;
> +		goto out_unlock;
> +	}
> +
> +	ret = ipa_ep_alloc(client);
> +	if (ret < 0)
> +		goto out_unlock;
> +	ep_id = ret;
> +
> +	if (aggr_en == IPA_ENABLE_DEAGGR &&
> !ipa_endp_aggr_support(ep_id)) {
> +		ret = -ENOTSUPP;
> +		goto out_unlock;
> +	}
> +
> +	/* We really do want 0 metadata offset */
> +	ipa_endp_init_hdr_prod(ep_id, header_size, 0,
> length_offset);
> +	ipa_endp_init_hdr_ext_prod(ep_id, header_align);
> +	ipa_endp_init_mode_prod(ep_id, IPA_BASIC, dst_client);
> +	ipa_endp_init_aggr_prod(ep_id, aggr_en, aggr_type);
> +	ipa_endp_init_cfg_prod(ep_id, offload_type, header_offset);
> +	ipa_endp_init_seq_prod(ep_id);
> +	ipa_endp_init_deaggr_prod(ep_id);
> +	/* Enable source notification status for exception packets
> +	 * (i.e. QMAP commands) to be routed to modem.
> +	 */
> +	ipa_endp_status_prod(ep_id, true, IPA_CLIENT_Q6_WAN_CONS);
> +
> +	/* Use a deferred interrupting no-op to reduce completion
> interrupts */
> +	ipa_no_intr_init(ep_id);
> +
> +	ret = ipa_ep_setup(ep_id, channel_count, 1, 0,
> +			   apps_ipa_tx_complete_notify, dev);
> +	if (ret)
> +		ipa_ep_free(ep_id);
> +	else
> +		rmnet_ipa_ctx->wan_prod_ep_id = ep_id;
> +
> +out_unlock:
> +	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	return ret;
> +}
> +
> +/** ipa_wwan_add_mux_channel() - add a mux_id */
> +static int ipa_wwan_add_mux_channel(u32 mux_id)
> +{
> +	int ret;
> +	u32 i;
> +
> +	mutex_lock(&rmnet_ipa_ctx->mux_id_mutex);
> +
> +	if (rmnet_ipa_ctx->mux_id_count >= MUX_CHANNEL_MAX) {
> +		ret = -EFAULT;
> +		goto out;
> +	}
> +
> +	for (i = 0; i < rmnet_ipa_ctx->mux_id_count; i++)
> +		if (mux_id == rmnet_ipa_ctx->mux_id[i])
> +			break;
> +
> +	/* Record the mux_id if it hasn't already been seen */
> +	if (i == rmnet_ipa_ctx->mux_id_count)
> +		rmnet_ipa_ctx->mux_id[rmnet_ipa_ctx->mux_id_count++] 
> = mux_id;
> +	ret = 0;
> +out:
> +	mutex_unlock(&rmnet_ipa_ctx->mux_id_mutex);
> +
> +	return ret;
> +}
> +
> +/** ipa_wwan_ioctl_extended() - rmnet extended I/O control */
> +static int ipa_wwan_ioctl_extended(struct net_device *dev, void
> __user *data)
> +{
> +	struct rmnet_ioctl_extended_s edata = { };
> +	size_t size = sizeof(edata);
> +
> +	if (copy_from_user(&edata, data, size))
> +		return -EFAULT;
> +
> +	switch (edata.extended_ioctl) {
> +	case RMNET_IOCTL_GET_SUPPORTED_FEATURES:	/* Get
> features */
> +		edata.u.data = RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL;
> +		edata.u.data |=
> RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT;
> +		edata.u.data |=
> RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_EPID:			/* Get
> endpoint ID */
> +		edata.u.data = 1;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_DRIVER_NAME:		/* Get
> driver name */
> +		memcpy(&edata.u.if_name, rmnet_ipa_ctx->dev->name,
> IFNAMSIZ);
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_ADD_MUX_CHANNEL:		/* Add MUX
> ID */
> +		return
> ipa_wwan_add_mux_channel(edata.u.rmnet_mux_val.mux_id);
> +
> +	case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:	/* Egress
> data format */
> +		return handle_egress_format(dev, &edata) ? -EFAULT :
> 0;
> +
> +	case RMNET_IOCTL_SET_INGRESS_DATA_FORMAT:	/* Ingress
> format */
> +		return handle_ingress_format(dev, &edata) ? -EFAULT
> : 0;
> +
> +	case RMNET_IOCTL_GET_EP_PAIR:			/* Get
> endpoint pair */
> +		edata.u.ipa_ep_pair.consumer_pipe_num =
> +				ipa_client_ep_id(IPA_CLIENT_APPS_WAN
> _PROD);
> +		edata.u.ipa_ep_pair.producer_pipe_num =
> +				ipa_client_ep_id(IPA_CLIENT_APPS_WAN
> _CONS);
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_SG_SUPPORT:		/* Get SG
> support */
> +		edata.u.data = 1;	/* Scatter/gather is always
> supported */
> +		goto copy_out;
> +
> +	/* Unsupported requests */
> +	case RMNET_IOCTL_SET_MRU:			/* Set MRU
> */
> +	case RMNET_IOCTL_GET_MRU:			/* Get MRU
> */
> +	case RMNET_IOCTL_GET_AGGREGATION_COUNT:		/*
> Get agg count */
> +	case RMNET_IOCTL_SET_AGGREGATION_COUNT:		/*
> Set agg count */
> +	case RMNET_IOCTL_GET_AGGREGATION_SIZE:		/* Get
> agg size */
> +	case RMNET_IOCTL_SET_AGGREGATION_SIZE:		/* Set
> agg size */
> +	case RMNET_IOCTL_FLOW_CONTROL:			/* Do
> flow control */
> +	case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:	/* For
> legacy use */
> +	case RMNET_IOCTL_GET_HWSW_MAP:			/* Get
> HW/SW map */
> +	case RMNET_IOCTL_SET_RX_HEADROOM:		/* Set RX
> Headroom */
> +	case RMNET_IOCTL_SET_QOS_VERSION:		/* Set 8/6
> byte QoS */
> +	case RMNET_IOCTL_GET_QOS_VERSION:		/* Get 8/6
> byte QoS */
> +	case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:	/* Get QoS
> modes */
> +	case RMNET_IOCTL_SET_SLEEP_STATE:		/* Set
> sleep state */
> +	case RMNET_IOCTL_SET_XLAT_DEV_INFO:		/* xlat
> dev name */
> +	case RMNET_IOCTL_DEREGISTER_DEV:		/*
> Deregister netdev */
> +		return -ENOTSUPP;	/* Defined, but unsupported
> command */
> +
> +	default:
> +		return -EINVAL;		/* Invalid
> (unrecognized) command */
> +	}
> +
> +copy_out:
> +	return copy_to_user(data, &edata, size) ? -EFAULT : 0;
> +}
> +
> +/** ipa_wwan_ioctl() - I/O control for wwan network driver */
> +static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr,
> int cmd)
> +{
> +	struct rmnet_ioctl_data_s ioctl_data = { };
> +	void __user *data;
> +	size_t size;
> +
> +	data = ifr->ifr_ifru.ifru_data;
> +	size = sizeof(ioctl_data);
> +
> +	switch (cmd) {
> +	/* These features are implied; alternatives are not
> supported */
> +	case RMNET_IOCTL_SET_LLP_IP:		/* RAW IP
> protocol */
> +	case RMNET_IOCTL_SET_QOS_DISABLE:	/* QoS header
> disabled */
> +		return 0;
> +
> +	/* These features are not supported; use alternatives */
> +	case RMNET_IOCTL_SET_LLP_ETHERNET:	/* Ethernet
> protocol */
> +	case RMNET_IOCTL_SET_QOS_ENABLE:	/* QoS header
> enabled */
> +	case RMNET_IOCTL_GET_OPMODE:		/* Get operation
> mode */
> +	case RMNET_IOCTL_FLOW_ENABLE:		/* Flow enable
> */
> +	case RMNET_IOCTL_FLOW_DISABLE:		/* Flow
> disable */
> +	case RMNET_IOCTL_FLOW_SET_HNDL:		/* Set flow
> handle */
> +		return -ENOTSUPP;
> +
> +	case RMNET_IOCTL_GET_LLP:		/* Get link
> protocol */
> +		ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_QOS:		/* Get QoS header
> state */
> +		ioctl_data.u.operation_mode = RMNET_MODE_NONE;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_OPEN:			/* Open
> transport port */
> +	case RMNET_IOCTL_CLOSE:			/* Close
> transport port */
> +		return 0;
> +
> +	case RMNET_IOCTL_EXTENDED:		/* Extended IOCTLs
> */
> +		return ipa_wwan_ioctl_extended(dev, data);
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +copy_out:
> +	return copy_to_user(data, &ioctl_data, size) ? -EFAULT : 0;
> +}
> +
> +static const struct net_device_ops ipa_wwan_ops_ip = {
> +	.ndo_open	= ipa_wwan_open,
> +	.ndo_stop	= ipa_wwan_stop,
> +	.ndo_start_xmit	= ipa_wwan_xmit,
> +	.ndo_do_ioctl	= ipa_wwan_ioctl,
> +};
> +
> +/** wwan_setup() - Setup the wwan network driver */
> +static void ipa_wwan_setup(struct net_device *dev)
> +{
> +	dev->netdev_ops = &ipa_wwan_ops_ip;
> +	ether_setup(dev);
> +	dev->header_ops = NULL;	 /* No header (override
> ether_setup() value) */
> +	dev->type = ARPHRD_RAWIP;
> +	dev->hard_header_len = 0;
> +	dev->max_mtu = WWAN_DATA_LEN;
> +	dev->mtu = dev->max_mtu;
> +	dev->addr_len = 0;
> +	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
> +	dev->needed_headroom = HEADROOM_FOR_QMAP;
> +	dev->needed_tailroom = TAILROOM;
> +	dev->watchdog_timeo = msecs_to_jiffies(10 * MSEC_PER_SEC);
> +}
> +
> +/** ipa_wwan_probe() - Network probe function */
> +static int ipa_wwan_probe(struct platform_device *pdev)
> +{
> +	struct ipa_wwan_private *wwan_ptr;
> +	struct net_device *dev;
> +	int ret;
> +
> +	mutex_init(&rmnet_ipa_ctx->ep_setup_mutex);
> +	mutex_init(&rmnet_ipa_ctx->mux_id_mutex);
> +
> +	/* Mark client handles bad until we initialize them */
> +	rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
> +	rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
> +
> +	ret = ipa_modem_smem_init();
> +	if (ret)
> +		goto err_clear_ctx;
> +
> +	/* start A7 QMI service/client */
> +	ipa_qmi_init();
> +
> +	/* initialize wan-driver netdev */
> +	dev = alloc_netdev(sizeof(struct ipa_wwan_private),
> +			   IPA_WWAN_DEV_NAME,
> +			   NET_NAME_UNKNOWN,
> +			   ipa_wwan_setup);
> +	if (!dev) {
> +		ipa_err("no memory for netdev\n");
> +		ret = -ENOMEM;
> +		goto err_clear_ctx;
> +	}
> +	rmnet_ipa_ctx->dev = dev;
> +	wwan_ptr = netdev_priv(dev);
> +	wwan_ptr->outstanding_high_ctl =
> DEFAULT_OUTSTANDING_HIGH_CTL;
> +	wwan_ptr->outstanding_high = DEFAULT_OUTSTANDING_HIGH;
> +	wwan_ptr->outstanding_low = DEFAULT_OUTSTANDING_LOW;
> +	atomic_set(&wwan_ptr->outstanding_pkts, 0);
> +
> +	/* Enable SG support in netdevice. */
> +	dev->hw_features |= NETIF_F_SG;
> +
> +	netif_napi_add(dev, &wwan_ptr->napi, ipa_rmnet_poll,
> NAPI_WEIGHT);
> +	ret = register_netdev(dev);
> +	if (ret) {
> +		ipa_err("unable to register ipa_netdev %d rc=%d\n",
> 0, ret);
> +		goto err_napi_del;
> +	}
> +
> +	/* offline charging mode */
> +	ipa_proxy_clk_unvote();
> +
> +	/* Till the system is suspended, we keep the clock open */
> +	ipa_client_add();
> +
> +	initialized = true;
> +
> +	return 0;
> +
> +err_napi_del:
> +	netif_napi_del(&wwan_ptr->napi);
> +	free_netdev(dev);
> +err_clear_ctx:
> +	memset(&rmnet_ipa_ctx_struct, 0,
> sizeof(rmnet_ipa_ctx_struct));
> +
> +	return ret;
> +}
> +
> +static int ipa_wwan_remove(struct platform_device *pdev)
> +{
> +	struct ipa_wwan_private *wwan_ptr =
> netdev_priv(rmnet_ipa_ctx->dev);
> +
> +	dev_info(&pdev->dev, "rmnet_ipa started
> deinitialization\n");
> +
> +	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	ipa_client_add();
> +
> +	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
> +		ipa_ep_teardown(rmnet_ipa_ctx->wan_cons_ep_id);
> +		rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
> +	}
> +
> +	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
> +		ipa_ep_teardown(rmnet_ipa_ctx->wan_prod_ep_id);
> +		rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
> +	}
> +
> +	ipa_client_remove();
> +
> +	netif_napi_del(&wwan_ptr->napi);
> +	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
> +	unregister_netdev(rmnet_ipa_ctx->dev);
> +
> +	if (rmnet_ipa_ctx->dev)
> +		free_netdev(rmnet_ipa_ctx->dev);
> +	rmnet_ipa_ctx->dev = NULL;
> +
> +	mutex_destroy(&rmnet_ipa_ctx->mux_id_mutex);
> +	mutex_destroy(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	initialized = false;
> +
> +	dev_info(&pdev->dev, "rmnet_ipa completed
> deinitialization\n");
> +
> +	return 0;
> +}
> +
> +/** rmnet_ipa_ap_suspend() - suspend callback for runtime_pm
> + * @dev: pointer to device
> + *
> + * This callback will be invoked by the runtime_pm framework when an
> AP suspend
> + * operation is invoked, usually by pressing a suspend button.
> + *
> + * Returns -EAGAIN to runtime_pm framework in case there are pending
> packets
> + * in the Tx queue. This will postpone the suspend operation until
> all the
> + * pending packets will be transmitted.
> + *
> + * In case there are no packets to send, releases the WWAN0_PROD
> entity.
> + * As an outcome, the number of IPA active clients should be
> decremented
> + * until IPA clocks can be gated.
> + */
> +static int rmnet_ipa_ap_suspend(struct device *dev)
> +{
> +	struct net_device *netdev = rmnet_ipa_ctx->dev;
> +	struct ipa_wwan_private *wwan_ptr;
> +	int ret;
> +
> +	if (!netdev) {
> +		ipa_err("netdev is NULL.\n");
> +		ret = 0;
> +		goto bail;
> +	}
> +
> +	netif_tx_lock_bh(netdev);
> +	wwan_ptr = netdev_priv(netdev);
> +	if (!wwan_ptr) {
> +		ipa_err("wwan_ptr is NULL.\n");
> +		ret = 0;
> +		goto unlock_and_bail;
> +	}
> +
> +	/* Do not allow A7 to suspend in case there are outstanding
> packets */
> +	if (atomic_read(&wwan_ptr->outstanding_pkts) != 0) {
> +		ret = -EAGAIN;
> +		goto unlock_and_bail;
> +	}
> +
> +	/* Make sure that there is no Tx operation ongoing */
> +	netif_stop_queue(netdev);
> +
> +	ret = 0;
> +	ipa_client_remove();
> +
> +unlock_and_bail:
> +	netif_tx_unlock_bh(netdev);
> +bail:
> +
> +	return ret;
> +}
> +
> +/** rmnet_ipa_ap_resume() - resume callback for runtime_pm
> + * @dev: pointer to device
> + *
> + * This callback will be invoked by the runtime_pm framework when an
> AP resume
> + * operation is invoked.
> + *
> + * Enables the network interface queue and returns success to the
> + * runtime_pm framework.
> + */
> +static int rmnet_ipa_ap_resume(struct device *dev)
> +{
> +	struct net_device *netdev = rmnet_ipa_ctx->dev;
> +
> +	ipa_client_add();
> +	if (netdev)
> +		netif_wake_queue(netdev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id rmnet_ipa_dt_match[] = {
> +	{.compatible = "qcom,rmnet-ipa"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, rmnet_ipa_dt_match);
> +
> +static const struct dev_pm_ops rmnet_ipa_pm_ops = {
> +	.suspend_noirq = rmnet_ipa_ap_suspend,
> +	.resume_noirq = rmnet_ipa_ap_resume,
> +};
> +
> +static struct platform_driver rmnet_ipa_driver = {
> +	.driver = {
> +		.name = "rmnet_ipa",
> +		.owner = THIS_MODULE,
> +		.pm = &rmnet_ipa_pm_ops,
> +		.of_match_table = rmnet_ipa_dt_match,
> +	},
> +	.probe = ipa_wwan_probe,
> +	.remove = ipa_wwan_remove,
> +};
> +
> +int ipa_wwan_init(void)
> +{
> +	if (initialized)
> +		return 0;
> +
> +	return platform_driver_register(&rmnet_ipa_driver);
> +}
> +
> +void ipa_wwan_cleanup(void)
> +{
> +	platform_driver_unregister(&rmnet_ipa_driver);
> +	memset(&rmnet_ipa_ctx_struct, 0,
> sizeof(rmnet_ipa_ctx_struct));
> +}
> +
> +static int ipa_rmnet_poll(struct napi_struct *napi, int budget)
> +{
> +	return ipa_rx_poll(rmnet_ipa_ctx->wan_cons_ep_id, budget);
> +}
> +
> +MODULE_DESCRIPTION("WWAN Network Interface");
> +MODULE_LICENSE("GPL v2");

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface
@ 2018-11-07 15:26     ` Dan Williams
  0 siblings, 0 replies; 76+ messages in thread
From: Dan Williams @ 2018-11-07 15:26 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, 2018-11-06 at 18:32 -0600, Alex Elder wrote:
> The IPA uses "rmnet" as a way to present remote network resources as
> if they were local to the AP.  IPA interfaces representing networks
> accessible via the modem are represented as rmnet interfaces,
> implemented by the "rmnet data driver" found here:
>     drivers/net/ethernet/qualcomm/rmnet/

It looks like there's a lot of overlap between this driver and that
one.  Ideally they would be a single driver which could automatically
select the IPA mode for appropriate hardware, or the IPA mode would be
a "subdriver" that bases itself heavily on the existing rmnet driver
even if it doesn't use the same packet movement functions.  Or
something like that.

But as Arnd stated, the ioctls won't fly.  They were also proposed for
the rmnet driver but they are better done as netlink, or via sysfs as
the existing qmi_wwan driver does for some of the same values.

Half the non-extended ioctls aren't even supported/used and should
simply be removed.

The extended ioctls should be evaluated as to whether they are really
needed (eg RMNET_IOCTL_GET_DRIVER_NAME).  I think most of the rest have
either been handled already via the 'rmnet' driver itself (like the MUX
channels using the vlan netlink attributes) or should be added via
netlink-type mechansims if they are really required.

In general, it would be good to get to a single 'rmnet' driver that has
a single API and supports as much hardware as possible.  There's too
much duplication currently.

Dan

> The IPA is able to perform aggregation of packets, as well as
> checksum offload.  These options (plus others, such as configuring
> MTU size) are configurable using an ioctl interface.  In addition,
> rmnet devices support multiplexing.
> 
> TX packets are handed to the data path layer, and when their
> transmission is complete the notification callback will be
> called.  The data path code posts RX packets to the hardware,
> and when they are filled they are supplied here by a receive
> notification callback.
> 
> The IPA driver currently does not support the modem shutting down
> (or crashing).  But the rmnet_ipa device roughly represents the
> availability of networks reachable by the modem.  If the modem is
> operational, an ipa_rmnet network device will be available.  Modem
> operation is managed by the remoteproc subsystem.
> 
> Note:  This portion of the driver will be heavily affected by
> planned rework on the data path code.
> 
> Signed-off-by: Alex Elder <elder@linaro.org>
> ---
>  drivers/net/ipa/msm_rmnet.h    | 120 +++++
>  drivers/net/ipa/rmnet_config.h |  31 ++
>  drivers/net/ipa/rmnet_ipa.c    | 805
> +++++++++++++++++++++++++++++++++
>  3 files changed, 956 insertions(+)
>  create mode 100644 drivers/net/ipa/msm_rmnet.h
>  create mode 100644 drivers/net/ipa/rmnet_config.h
>  create mode 100644 drivers/net/ipa/rmnet_ipa.c
> 
> diff --git a/drivers/net/ipa/msm_rmnet.h
> b/drivers/net/ipa/msm_rmnet.h
> new file mode 100644
> index 000000000000..042380fd53fb
> --- /dev/null
> +++ b/drivers/net/ipa/msm_rmnet.h
> @@ -0,0 +1,120 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +#ifndef _MSM_RMNET_H_
> +#define _MSM_RMNET_H_
> +
> +/* Bitmap macros for RmNET driver operation mode. */
> +#define RMNET_MODE_NONE	    0x00
> +#define RMNET_MODE_LLP_ETH  0x01
> +#define RMNET_MODE_LLP_IP   0x02
> +#define RMNET_MODE_QOS	    0x04
> +
> +/* IOCTL commands
> + * Values chosen to not conflict with other drivers in the ecosystem
> + */
> +
> +#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet
> protocol  */
> +#define RMNET_IOCTL_SET_LLP_IP	     0x000089f2 /* Set RAWIP
> protocol	  */
> +#define RMNET_IOCTL_GET_LLP	     0x000089f3 /* Get link
> protocol	  */
> +#define RMNET_IOCTL_SET_QOS_ENABLE   0x000089f4 /* Set QoS header
> enabled */
> +#define RMNET_IOCTL_SET_QOS_DISABLE  0x000089f5 /* Set QoS header
> disabled*/
> +#define RMNET_IOCTL_GET_QOS	     0x000089f6 /* Get QoS header
> state	  */
> +#define RMNET_IOCTL_GET_OPMODE	     0x000089f7 /* Get
> operation mode	  */
> +#define RMNET_IOCTL_OPEN	     0x000089f8 /* Open transport
> port	  */
> +#define RMNET_IOCTL_CLOSE	     0x000089f9 /* Close transport
> port	  */
> +#define RMNET_IOCTL_FLOW_ENABLE	     0x000089fa /* Flow
> enable		  */
> +#define RMNET_IOCTL_FLOW_DISABLE     0x000089fb /* Flow disable	
> 	  */
> +#define RMNET_IOCTL_FLOW_SET_HNDL    0x000089fc /* Set flow handle	
>   */
> +#define RMNET_IOCTL_EXTENDED	     0x000089fd /* Extended
> IOCTLs	  */
> +
> +/* RmNet Data Required IOCTLs */
> +#define RMNET_IOCTL_GET_SUPPORTED_FEATURES     0x0000	/* Get
> features	   */
> +#define RMNET_IOCTL_SET_MRU		       0x0001	/*
> Set MRU	   */
> +#define RMNET_IOCTL_GET_MRU		       0x0002	/*
> Get MRU	   */
> +#define RMNET_IOCTL_GET_EPID		       0x0003	/*
> Get endpoint ID */
> +#define RMNET_IOCTL_GET_DRIVER_NAME	       0x0004	/*
> Get driver name */
> +#define RMNET_IOCTL_ADD_MUX_CHANNEL	       0x0005	/*
> Add MUX ID	   */
> +#define RMNET_IOCTL_SET_EGRESS_DATA_FORMAT     0x0006	/* Set
> EDF	   */
> +#define RMNET_IOCTL_SET_INGRESS_DATA_FORMAT    0x0007	/* Set
> IDF	   */
> +#define RMNET_IOCTL_SET_AGGREGATION_COUNT      0x0008	/* Set
> agg count   */
> +#define RMNET_IOCTL_GET_AGGREGATION_COUNT      0x0009	/* Get
> agg count   */
> +#define RMNET_IOCTL_SET_AGGREGATION_SIZE       0x000a	/* Set
> agg size	   */
> +#define RMNET_IOCTL_GET_AGGREGATION_SIZE       0x000b	/* Get
> agg size	   */
> +#define RMNET_IOCTL_FLOW_CONTROL	       0x000c	/* Do
> flow control */
> +#define RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL   0x000d	/* For
> legacy use  */
> +#define RMNET_IOCTL_GET_HWSW_MAP	       0x000e	/* Get
> HW/SW map   */
> +#define RMNET_IOCTL_SET_RX_HEADROOM	       0x000f	/*
> RX Headroom	   */
> +#define RMNET_IOCTL_GET_EP_PAIR		       0x0010	
> /* Endpoint pair   */
> +#define RMNET_IOCTL_SET_QOS_VERSION	       0x0011	/*
> 8/6 byte QoS hdr*/
> +#define RMNET_IOCTL_GET_QOS_VERSION	       0x0012	/*
> 8/6 byte QoS hdr*/
> +#define RMNET_IOCTL_GET_SUPPORTED_QOS_MODES    0x0013	/* Get
> QoS modes   */
> +#define RMNET_IOCTL_SET_SLEEP_STATE	       0x0014	/*
> Set sleep state */
> +#define RMNET_IOCTL_SET_XLAT_DEV_INFO	       0x0015	/*
> xlat dev name   */
> +#define RMNET_IOCTL_DEREGISTER_DEV	       0x0016	/*
> Dereg a net dev */
> +#define RMNET_IOCTL_GET_SG_SUPPORT	       0x0017	/*
> Query sg support*/
> +
> +/* Return values for the RMNET_IOCTL_GET_SUPPORTED_FEATURES IOCTL */
> +#define RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL		BIT(0)
> +#define RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT		BIT(1
> )
> +#define RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT	BIT(2)
> +
> +/* Input values for the RMNET_IOCTL_SET_EGRESS_DATA_FORMAT IOCTL  */
> +#define RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION		BIT(2)
> +#define RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM		BIT(4)
> +
> +/* Input values for the RMNET_IOCTL_SET_INGRESS_DATA_FORMAT IOCTL */
> +#define RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM		BIT(4)
> +#define RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA		BIT(5)
> +
> +/* User space may not have this defined. */
> +#ifndef IFNAMSIZ
> +#define IFNAMSIZ 16
> +#endif
> +
> +struct rmnet_ioctl_extended_s {
> +	u32	extended_ioctl;
> +	union {
> +		u32	data; /* Generic data field for most
> extended IOCTLs */
> +
> +		/* Return values for
> +		 *    RMNET_IOCTL_GET_DRIVER_NAME
> +		 *    RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL
> +		 */
> +		char	if_name[IFNAMSIZ];
> +
> +		/* Input values for the RMNET_IOCTL_ADD_MUX_CHANNEL
> IOCTL */
> +		struct {
> +			u32	mux_id;
> +			char	vchannel_name[IFNAMSIZ];
> +		} rmnet_mux_val;
> +
> +		/* Input values for the RMNET_IOCTL_FLOW_CONTROL
> IOCTL */
> +		struct {
> +			u8	flow_mode;
> +			u8	mux_id;
> +		} flow_control_prop;
> +
> +		/* Return values for RMNET_IOCTL_GET_EP_PAIR */
> +		struct {
> +			u32	consumer_pipe_num;
> +			u32	producer_pipe_num;
> +		} ipa_ep_pair;
> +
> +		struct {
> +			u32	__data; /* Placeholder for legacy
> data*/
> +			u32	agg_size;
> +			u32	agg_count;
> +		} ingress_format;
> +	} u;
> +};
> +
> +struct rmnet_ioctl_data_s {
> +	union {
> +		u32	operation_mode;
> +		u32	tcm_handle;
> +	} u;
> +};
> +#endif /* _MSM_RMNET_H_ */
> diff --git a/drivers/net/ipa/rmnet_config.h
> b/drivers/net/ipa/rmnet_config.h
> new file mode 100644
> index 000000000000..3b9a549ca1bd
> --- /dev/null
> +++ b/drivers/net/ipa/rmnet_config.h
> @@ -0,0 +1,31 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2016-2018, The Linux Foundation. All rights
> reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +#ifndef _RMNET_CONFIG_H_
> +#define _RMNET_CONFIG_H_
> +
> +#include <linux/types.h>
> +
> +/* XXX We want to use struct rmnet_map_header, but that's currently
> defined in
> + * XXX     drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
> + * XXX We also want to use RMNET_MAP_GET_CD_BIT(Y), defined in the
> same file.
> + */
> +struct rmnet_map_header_s {
> +#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
> +	u8	pad_len		: 6,
> +		reserved_bit	: 1,
> +		cd_bit		: 1;
> +#else
> +	u8	cd_bit		: 1,
> +		reserved_bit	: 1,
> +		pad_len		: 6;
> +#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
> +	u8	mux_id;
> +	u16	pkt_len;
> +}  __aligned(1);
> +
> +#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *)Y-
> >data)->cd_bit)
> +
> +#endif /* _RMNET_CONFIG_H_ */
> diff --git a/drivers/net/ipa/rmnet_ipa.c
> b/drivers/net/ipa/rmnet_ipa.c
> new file mode 100644
> index 000000000000..7006afe3a5ea
> --- /dev/null
> +++ b/drivers/net/ipa/rmnet_ipa.c
> @@ -0,0 +1,805 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (c) 2014-2018, The Linux Foundation. All rights
> reserved.
> + * Copyright (C) 2018 Linaro Ltd.
> + */
> +
> +/* WWAN Transport Network Driver. */
> +
> +#include <linux/completion.h>
> +#include <linux/errno.h>
> +#include <linux/if_arp.h>
> +#include <linux/interrupt.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/of_device.h>
> +#include <linux/string.h>
> +#include <linux/skbuff.h>
> +#include <linux/version.h>
> +#include <linux/workqueue.h>
> +#include <net/pkt_sched.h>
> +
> +#include "msm_rmnet.h"
> +#include "rmnet_config.h"
> +#include "ipa_qmi.h"
> +#include "ipa_i.h"
> +
> +#define DRIVER_NAME		"wwan_ioctl"
> +#define IPA_WWAN_DEV_NAME	"rmnet_ipa%d"
> +
> +#define MUX_CHANNEL_MAX		10	/* max mux channels
> */
> +
> +#define NAPI_WEIGHT		60
> +
> +#define WWAN_DATA_LEN		2000
> +#define HEADROOM_FOR_QMAP	8	/* for mux header */
> +#define TAILROOM		0	/* for padding by mux layer
> */
> +
> +#define DEFAULT_OUTSTANDING_HIGH	128
> +#define DEFAULT_OUTSTANDING_HIGH_CTL	(DEFAULT_OUTSTANDING_HIG
> H + 32)
> +#define DEFAULT_OUTSTANDING_LOW		64
> +
> +#define IPA_APPS_WWAN_CONS_RING_COUNT	256
> +#define IPA_APPS_WWAN_PROD_RING_COUNT	512
> +
> +static int ipa_rmnet_poll(struct napi_struct *napi, int budget);
> +
> +/** struct ipa_wwan_private - WWAN private data
> + * @net: network interface struct implemented by this driver
> + * @stats: iface statistics
> + * @outstanding_high: number of outstanding packets allowed
> + * @outstanding_low: number of outstanding packets which shall cause
> + *
> + * WWAN private - holds all relevant info about WWAN driver
> + */
> +struct ipa_wwan_private {
> +	struct net_device_stats stats;
> +	atomic_t outstanding_pkts;
> +	int outstanding_high_ctl;
> +	int outstanding_high;
> +	int outstanding_low;
> +	struct napi_struct napi;
> +};
> +
> +struct rmnet_ipa_context {
> +	struct net_device *dev;
> +	struct mutex mux_id_mutex;		/* protects
> mux_id[] */
> +	u32 mux_id_count;
> +	u32 mux_id[MUX_CHANNEL_MAX];
> +	u32 wan_prod_ep_id;
> +	u32 wan_cons_ep_id;
> +	struct mutex ep_setup_mutex;		/* endpoint
> setup/teardown */
> +};
> +
> +static bool initialized;	/* Avoid duplicate initialization */
> +
> +static struct rmnet_ipa_context rmnet_ipa_ctx_struct;
> +static struct rmnet_ipa_context *rmnet_ipa_ctx =
> &rmnet_ipa_ctx_struct;
> +
> +/** wwan_open() - Opens the wwan network interface */
> +static int ipa_wwan_open(struct net_device *dev)
> +{
> +	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
> +
> +	napi_enable(&wwan_ptr->napi);
> +	netif_start_queue(dev);
> +
> +	return 0;
> +}
> +
> +/** ipa_wwan_stop() - Stops the wwan network interface. */
> +static int ipa_wwan_stop(struct net_device *dev)
> +{
> +	netif_stop_queue(dev);
> +
> +	return 0;
> +}
> +
> +/** ipa_wwan_xmit() - Transmits an skb.
> + *
> + * @skb: skb to be transmitted
> + * @dev: network device
> + *
> + * Return codes:
> + * NETDEV_TX_OK: Success
> + * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later
> + */
> +static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device
> *dev)
> +{
> +	struct ipa_wwan_private *wwan_ptr = netdev_priv(dev);
> +	unsigned int skb_len;
> +	int outstanding;
> +
> +	if (skb->protocol != htons(ETH_P_MAP)) {
> +		dev_kfree_skb_any(skb);
> +		dev->stats.tx_dropped++;
> +		return NETDEV_TX_OK;
> +	}
> +
> +	/* Control packets are sent even if queue is stopped.  We
> +	 * always honor the data and control high-water marks.
> +	 */
> +	outstanding = atomic_read(&wwan_ptr->outstanding_pkts);
> +	if (!RMNET_MAP_GET_CD_BIT(skb)) {	/* Data packet? */
> +		if (netif_queue_stopped(dev))
> +			return NETDEV_TX_BUSY;
> +		if (outstanding >= wwan_ptr->outstanding_high)
> +			return NETDEV_TX_BUSY;
> +	} else if (outstanding >= wwan_ptr->outstanding_high_ctl) {
> +		return NETDEV_TX_BUSY;
> +	}
> +
> +	/* both data packets and commands will be routed to
> +	 * IPA_CLIENT_Q6_WAN_CONS based on status configuration.
> +	 */
> +	skb_len = skb->len;
> +	if (ipa_tx_dp(IPA_CLIENT_APPS_WAN_PROD, skb))
> +		return NETDEV_TX_BUSY;
> +
> +	atomic_inc(&wwan_ptr->outstanding_pkts);
> +	dev->stats.tx_packets++;
> +	dev->stats.tx_bytes += skb_len;
> +
> +	return NETDEV_TX_OK;
> +}
> +
> +/** apps_ipa_tx_complete_notify() - Rx notify
> + *
> + * @priv: driver context
> + * @evt: event type
> + * @data: data provided with event
> + *
> + * Check that the packet is the one we sent and release it
> + * This function will be called in defered context in IPA wq.
> + */
> +static void apps_ipa_tx_complete_notify(void *priv, enum
> ipa_dp_evt_type evt,
> +					unsigned long data)
> +{
> +	struct ipa_wwan_private *wwan_ptr;
> +	struct net_device *dev = priv;
> +	struct sk_buff *skb;
> +
> +	skb = (struct sk_buff *)data;
> +
> +	if (dev != rmnet_ipa_ctx->dev) {
> +		dev_kfree_skb_any(skb);
> +		return;
> +	}
> +
> +	if (evt != IPA_WRITE_DONE) {
> +		ipa_err("unsupported evt on Tx callback, Drop the
> packet\n");
> +		dev_kfree_skb_any(skb);
> +		dev->stats.tx_dropped++;
> +		return;
> +	}
> +
> +	wwan_ptr = netdev_priv(dev);
> +	atomic_dec(&wwan_ptr->outstanding_pkts);
> +	__netif_tx_lock_bh(netdev_get_tx_queue(dev, 0));
> +	if (netif_queue_stopped(dev) &&
> +	    atomic_read(&wwan_ptr->outstanding_pkts) <
> +				wwan_ptr->outstanding_low) {
> +		netif_wake_queue(dev);
> +	}
> +
> +	__netif_tx_unlock_bh(netdev_get_tx_queue(dev, 0));
> +	dev_kfree_skb_any(skb);
> +}
> +
> +/** apps_ipa_packet_receive_notify() - Rx notify
> + *
> + * @priv: driver context
> + * @evt: event type
> + * @data: data provided with event
> + *
> + * IPA will pass a packet to the Linux network stack with skb->data
> + */
> +static void apps_ipa_packet_receive_notify(void *priv, enum
> ipa_dp_evt_type evt,
> +					   unsigned long data)
> +{
> +	struct ipa_wwan_private *wwan_ptr;
> +	struct net_device *dev = priv;
> +
> +	wwan_ptr = netdev_priv(dev);
> +	if (evt == IPA_RECEIVE) {
> +		struct sk_buff *skb = (struct sk_buff *)data;
> +		int ret;
> +		unsigned int packet_len = skb->len;
> +
> +		skb->dev = rmnet_ipa_ctx->dev;
> +		skb->protocol = htons(ETH_P_MAP);
> +
> +		ret = netif_receive_skb(skb);
> +		if (ret) {
> +			pr_err_ratelimited("fail on
> netif_receive_skb\n");
> +			dev->stats.rx_dropped++;
> +		}
> +		dev->stats.rx_packets++;
> +		dev->stats.rx_bytes += packet_len;
> +	} else if (evt == IPA_CLIENT_START_POLL) {
> +		napi_schedule(&wwan_ptr->napi);
> +	} else if (evt == IPA_CLIENT_COMP_NAPI) {
> +		napi_complete(&wwan_ptr->napi);
> +	} else {
> +		ipa_err("Invalid evt %d received in
> wan_ipa_receive\n", evt);
> +	}
> +}
> +
> +/** handle_ingress_format() - Ingress data format configuration */
> +static int handle_ingress_format(struct net_device *dev,
> +				 struct rmnet_ioctl_extended_s *in)
> +{
> +	enum ipa_cs_offload_en offload_type;
> +	enum ipa_client_type client;
> +	u32 metadata_offset;
> +	u32 rx_buffer_size;
> +	u32 channel_count;
> +	u32 length_offset;
> +	u32 header_size;
> +	bool aggr_active;
> +	u32 aggr_bytes;
> +	u32 aggr_count;
> +	u32 aggr_size;	/* in KB */
> +	u32 ep_id;
> +	int ret;
> +
> +	client = IPA_CLIENT_APPS_WAN_CONS;
> +	channel_count = IPA_APPS_WWAN_CONS_RING_COUNT;
> +	header_size = sizeof(struct rmnet_map_header_s);
> +	metadata_offset = offsetof(struct rmnet_map_header_s,
> mux_id);
> +	length_offset = offsetof(struct rmnet_map_header_s,
> pkt_len);
> +	offload_type = IPA_CS_OFFLOAD_NONE;
> +	aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT;
> +	aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT;
> +	aggr_active = false;
> +
> +	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM)
> +		offload_type = IPA_CS_OFFLOAD_DL;
> +
> +	if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA) {
> +		aggr_bytes = in->u.ingress_format.agg_size;
> +		aggr_count = in->u.ingress_format.agg_count;
> +		aggr_active = true;
> +	}
> +
> +	if (aggr_bytes > ipa_reg_aggr_max_byte_limit())
> +		return -EINVAL;
> +
> +	if (aggr_count > ipa_reg_aggr_max_packet_limit())
> +		return -EINVAL;
> +
> +	/* Compute the buffer size required to handle the requested
> +	 * aggregation byte limit.  The aggr_byte_limit value is
> +	 * expressed as a number of KB, but we derive that value
> +	 * after computing the buffer size to use (in bytes).  The
> +	 * buffer must be sufficient to hold one IPA_MTU-sized
> +	 * packet *after* the limit is reached.
> +	 *
> +	 * (Note that the rx_buffer_size value reflects only the
> +	 * space for data, not any standard metadata or headers.)
> +	 */
> +	rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes);
> +
> +	/* Account for the extra IPA_MTU past the limit in the
> +	 * buffer, and convert the result to the KB units the
> +	 * aggr_byte_limit uses.
> +	 */
> +	aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K;
> +
> +	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
> +		ret = -EBUSY;
> +		goto out_unlock;
> +	}
> +
> +	ret = ipa_ep_alloc(client);
> +	if (ret < 0)
> +		goto out_unlock;
> +	ep_id = ret;
> +
> +	/* Record our endpoint configuration parameters */
> +	ipa_endp_init_hdr_cons(ep_id, header_size, metadata_offset,
> +			       length_offset);
> +	ipa_endp_init_hdr_ext_cons(ep_id, 0, true);
> +	ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, true);
> +	ipa_endp_init_cfg_cons(ep_id, offload_type);
> +	ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0xff000000);
> +	ipa_endp_status_cons(ep_id, !aggr_active);
> +
> +	ipa_ctx->ipa_client_apps_wan_cons_agg_gro = aggr_active;
> +
> +	ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size,
> +			   apps_ipa_packet_receive_notify, dev);
> +	if (ret)
> +		ipa_ep_free(ep_id);
> +	else
> +		rmnet_ipa_ctx->wan_cons_ep_id = ep_id;
> +out_unlock:
> +	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	return ret;
> +}
> +
> +/** handle_egress_format() - Egress data format configuration */
> +static int handle_egress_format(struct net_device *dev,
> +				struct rmnet_ioctl_extended_s *e)
> +{
> +	enum ipa_cs_offload_en offload_type;
> +	enum ipa_client_type dst_client;
> +	enum ipa_client_type client;
> +	enum ipa_aggr_type aggr_type;
> +	enum ipa_aggr_en aggr_en;
> +	u32 channel_count;
> +	u32 length_offset;
> +	u32 header_align;
> +	u32 header_offset;
> +	u32 header_size;
> +	u32 ep_id;
> +	int ret;
> +
> +	client = IPA_CLIENT_APPS_WAN_PROD;
> +	dst_client = IPA_CLIENT_APPS_LAN_CONS;
> +	channel_count = IPA_APPS_WWAN_PROD_RING_COUNT;
> +	header_size = sizeof(struct rmnet_map_header_s);
> +	offload_type = IPA_CS_OFFLOAD_NONE;
> +	aggr_en = IPA_BYPASS_AGGR;
> +	aggr_type = 0;	/* ignored if BYPASS */
> +	header_offset = 0;
> +	length_offset = 0;
> +	header_align = 0;
> +
> +	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM) {
> +		offload_type = IPA_CS_OFFLOAD_UL;
> +		header_offset = sizeof(struct rmnet_map_header_s) /
> 4;
> +		header_size += sizeof(u32);
> +	}
> +
> +	if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION) {
> +		aggr_en = IPA_ENABLE_DEAGGR;
> +		aggr_type = IPA_QCMAP;
> +		length_offset = offsetof(struct rmnet_map_header_s,
> pkt_len);
> +		header_align = ilog2(sizeof(u32));
> +	}
> +
> +	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
> +		ret = -EBUSY;
> +		goto out_unlock;
> +	}
> +
> +	ret = ipa_ep_alloc(client);
> +	if (ret < 0)
> +		goto out_unlock;
> +	ep_id = ret;
> +
> +	if (aggr_en == IPA_ENABLE_DEAGGR &&
> !ipa_endp_aggr_support(ep_id)) {
> +		ret = -ENOTSUPP;
> +		goto out_unlock;
> +	}
> +
> +	/* We really do want 0 metadata offset */
> +	ipa_endp_init_hdr_prod(ep_id, header_size, 0,
> length_offset);
> +	ipa_endp_init_hdr_ext_prod(ep_id, header_align);
> +	ipa_endp_init_mode_prod(ep_id, IPA_BASIC, dst_client);
> +	ipa_endp_init_aggr_prod(ep_id, aggr_en, aggr_type);
> +	ipa_endp_init_cfg_prod(ep_id, offload_type, header_offset);
> +	ipa_endp_init_seq_prod(ep_id);
> +	ipa_endp_init_deaggr_prod(ep_id);
> +	/* Enable source notification status for exception packets
> +	 * (i.e. QMAP commands) to be routed to modem.
> +	 */
> +	ipa_endp_status_prod(ep_id, true, IPA_CLIENT_Q6_WAN_CONS);
> +
> +	/* Use a deferred interrupting no-op to reduce completion
> interrupts */
> +	ipa_no_intr_init(ep_id);
> +
> +	ret = ipa_ep_setup(ep_id, channel_count, 1, 0,
> +			   apps_ipa_tx_complete_notify, dev);
> +	if (ret)
> +		ipa_ep_free(ep_id);
> +	else
> +		rmnet_ipa_ctx->wan_prod_ep_id = ep_id;
> +
> +out_unlock:
> +	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	return ret;
> +}
> +
> +/** ipa_wwan_add_mux_channel() - add a mux_id */
> +static int ipa_wwan_add_mux_channel(u32 mux_id)
> +{
> +	int ret;
> +	u32 i;
> +
> +	mutex_lock(&rmnet_ipa_ctx->mux_id_mutex);
> +
> +	if (rmnet_ipa_ctx->mux_id_count >= MUX_CHANNEL_MAX) {
> +		ret = -EFAULT;
> +		goto out;
> +	}
> +
> +	for (i = 0; i < rmnet_ipa_ctx->mux_id_count; i++)
> +		if (mux_id == rmnet_ipa_ctx->mux_id[i])
> +			break;
> +
> +	/* Record the mux_id if it hasn't already been seen */
> +	if (i == rmnet_ipa_ctx->mux_id_count)
> +		rmnet_ipa_ctx->mux_id[rmnet_ipa_ctx->mux_id_count++] 
> = mux_id;
> +	ret = 0;
> +out:
> +	mutex_unlock(&rmnet_ipa_ctx->mux_id_mutex);
> +
> +	return ret;
> +}
> +
> +/** ipa_wwan_ioctl_extended() - rmnet extended I/O control */
> +static int ipa_wwan_ioctl_extended(struct net_device *dev, void
> __user *data)
> +{
> +	struct rmnet_ioctl_extended_s edata = { };
> +	size_t size = sizeof(edata);
> +
> +	if (copy_from_user(&edata, data, size))
> +		return -EFAULT;
> +
> +	switch (edata.extended_ioctl) {
> +	case RMNET_IOCTL_GET_SUPPORTED_FEATURES:	/* Get
> features */
> +		edata.u.data = RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL;
> +		edata.u.data |=
> RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT;
> +		edata.u.data |=
> RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_EPID:			/* Get
> endpoint ID */
> +		edata.u.data = 1;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_DRIVER_NAME:		/* Get
> driver name */
> +		memcpy(&edata.u.if_name, rmnet_ipa_ctx->dev->name,
> IFNAMSIZ);
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_ADD_MUX_CHANNEL:		/* Add MUX
> ID */
> +		return
> ipa_wwan_add_mux_channel(edata.u.rmnet_mux_val.mux_id);
> +
> +	case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:	/* Egress
> data format */
> +		return handle_egress_format(dev, &edata) ? -EFAULT :
> 0;
> +
> +	case RMNET_IOCTL_SET_INGRESS_DATA_FORMAT:	/* Ingress
> format */
> +		return handle_ingress_format(dev, &edata) ? -EFAULT
> : 0;
> +
> +	case RMNET_IOCTL_GET_EP_PAIR:			/* Get
> endpoint pair */
> +		edata.u.ipa_ep_pair.consumer_pipe_num =
> +				ipa_client_ep_id(IPA_CLIENT_APPS_WAN
> _PROD);
> +		edata.u.ipa_ep_pair.producer_pipe_num =
> +				ipa_client_ep_id(IPA_CLIENT_APPS_WAN
> _CONS);
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_SG_SUPPORT:		/* Get SG
> support */
> +		edata.u.data = 1;	/* Scatter/gather is always
> supported */
> +		goto copy_out;
> +
> +	/* Unsupported requests */
> +	case RMNET_IOCTL_SET_MRU:			/* Set MRU
> */
> +	case RMNET_IOCTL_GET_MRU:			/* Get MRU
> */
> +	case RMNET_IOCTL_GET_AGGREGATION_COUNT:		/*
> Get agg count */
> +	case RMNET_IOCTL_SET_AGGREGATION_COUNT:		/*
> Set agg count */
> +	case RMNET_IOCTL_GET_AGGREGATION_SIZE:		/* Get
> agg size */
> +	case RMNET_IOCTL_SET_AGGREGATION_SIZE:		/* Set
> agg size */
> +	case RMNET_IOCTL_FLOW_CONTROL:			/* Do
> flow control */
> +	case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:	/* For
> legacy use */
> +	case RMNET_IOCTL_GET_HWSW_MAP:			/* Get
> HW/SW map */
> +	case RMNET_IOCTL_SET_RX_HEADROOM:		/* Set RX
> Headroom */
> +	case RMNET_IOCTL_SET_QOS_VERSION:		/* Set 8/6
> byte QoS */
> +	case RMNET_IOCTL_GET_QOS_VERSION:		/* Get 8/6
> byte QoS */
> +	case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:	/* Get QoS
> modes */
> +	case RMNET_IOCTL_SET_SLEEP_STATE:		/* Set
> sleep state */
> +	case RMNET_IOCTL_SET_XLAT_DEV_INFO:		/* xlat
> dev name */
> +	case RMNET_IOCTL_DEREGISTER_DEV:		/*
> Deregister netdev */
> +		return -ENOTSUPP;	/* Defined, but unsupported
> command */
> +
> +	default:
> +		return -EINVAL;		/* Invalid
> (unrecognized) command */
> +	}
> +
> +copy_out:
> +	return copy_to_user(data, &edata, size) ? -EFAULT : 0;
> +}
> +
> +/** ipa_wwan_ioctl() - I/O control for wwan network driver */
> +static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr,
> int cmd)
> +{
> +	struct rmnet_ioctl_data_s ioctl_data = { };
> +	void __user *data;
> +	size_t size;
> +
> +	data = ifr->ifr_ifru.ifru_data;
> +	size = sizeof(ioctl_data);
> +
> +	switch (cmd) {
> +	/* These features are implied; alternatives are not
> supported */
> +	case RMNET_IOCTL_SET_LLP_IP:		/* RAW IP
> protocol */
> +	case RMNET_IOCTL_SET_QOS_DISABLE:	/* QoS header
> disabled */
> +		return 0;
> +
> +	/* These features are not supported; use alternatives */
> +	case RMNET_IOCTL_SET_LLP_ETHERNET:	/* Ethernet
> protocol */
> +	case RMNET_IOCTL_SET_QOS_ENABLE:	/* QoS header
> enabled */
> +	case RMNET_IOCTL_GET_OPMODE:		/* Get operation
> mode */
> +	case RMNET_IOCTL_FLOW_ENABLE:		/* Flow enable
> */
> +	case RMNET_IOCTL_FLOW_DISABLE:		/* Flow
> disable */
> +	case RMNET_IOCTL_FLOW_SET_HNDL:		/* Set flow
> handle */
> +		return -ENOTSUPP;
> +
> +	case RMNET_IOCTL_GET_LLP:		/* Get link
> protocol */
> +		ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_GET_QOS:		/* Get QoS header
> state */
> +		ioctl_data.u.operation_mode = RMNET_MODE_NONE;
> +		goto copy_out;
> +
> +	case RMNET_IOCTL_OPEN:			/* Open
> transport port */
> +	case RMNET_IOCTL_CLOSE:			/* Close
> transport port */
> +		return 0;
> +
> +	case RMNET_IOCTL_EXTENDED:		/* Extended IOCTLs
> */
> +		return ipa_wwan_ioctl_extended(dev, data);
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +copy_out:
> +	return copy_to_user(data, &ioctl_data, size) ? -EFAULT : 0;
> +}
> +
> +static const struct net_device_ops ipa_wwan_ops_ip = {
> +	.ndo_open	= ipa_wwan_open,
> +	.ndo_stop	= ipa_wwan_stop,
> +	.ndo_start_xmit	= ipa_wwan_xmit,
> +	.ndo_do_ioctl	= ipa_wwan_ioctl,
> +};
> +
> +/** wwan_setup() - Setup the wwan network driver */
> +static void ipa_wwan_setup(struct net_device *dev)
> +{
> +	dev->netdev_ops = &ipa_wwan_ops_ip;
> +	ether_setup(dev);
> +	dev->header_ops = NULL;	 /* No header (override
> ether_setup() value) */
> +	dev->type = ARPHRD_RAWIP;
> +	dev->hard_header_len = 0;
> +	dev->max_mtu = WWAN_DATA_LEN;
> +	dev->mtu = dev->max_mtu;
> +	dev->addr_len = 0;
> +	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
> +	dev->needed_headroom = HEADROOM_FOR_QMAP;
> +	dev->needed_tailroom = TAILROOM;
> +	dev->watchdog_timeo = msecs_to_jiffies(10 * MSEC_PER_SEC);
> +}
> +
> +/** ipa_wwan_probe() - Network probe function */
> +static int ipa_wwan_probe(struct platform_device *pdev)
> +{
> +	struct ipa_wwan_private *wwan_ptr;
> +	struct net_device *dev;
> +	int ret;
> +
> +	mutex_init(&rmnet_ipa_ctx->ep_setup_mutex);
> +	mutex_init(&rmnet_ipa_ctx->mux_id_mutex);
> +
> +	/* Mark client handles bad until we initialize them */
> +	rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
> +	rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
> +
> +	ret = ipa_modem_smem_init();
> +	if (ret)
> +		goto err_clear_ctx;
> +
> +	/* start A7 QMI service/client */
> +	ipa_qmi_init();
> +
> +	/* initialize wan-driver netdev */
> +	dev = alloc_netdev(sizeof(struct ipa_wwan_private),
> +			   IPA_WWAN_DEV_NAME,
> +			   NET_NAME_UNKNOWN,
> +			   ipa_wwan_setup);
> +	if (!dev) {
> +		ipa_err("no memory for netdev\n");
> +		ret = -ENOMEM;
> +		goto err_clear_ctx;
> +	}
> +	rmnet_ipa_ctx->dev = dev;
> +	wwan_ptr = netdev_priv(dev);
> +	wwan_ptr->outstanding_high_ctl =
> DEFAULT_OUTSTANDING_HIGH_CTL;
> +	wwan_ptr->outstanding_high = DEFAULT_OUTSTANDING_HIGH;
> +	wwan_ptr->outstanding_low = DEFAULT_OUTSTANDING_LOW;
> +	atomic_set(&wwan_ptr->outstanding_pkts, 0);
> +
> +	/* Enable SG support in netdevice. */
> +	dev->hw_features |= NETIF_F_SG;
> +
> +	netif_napi_add(dev, &wwan_ptr->napi, ipa_rmnet_poll,
> NAPI_WEIGHT);
> +	ret = register_netdev(dev);
> +	if (ret) {
> +		ipa_err("unable to register ipa_netdev %d rc=%d\n",
> 0, ret);
> +		goto err_napi_del;
> +	}
> +
> +	/* offline charging mode */
> +	ipa_proxy_clk_unvote();
> +
> +	/* Till the system is suspended, we keep the clock open */
> +	ipa_client_add();
> +
> +	initialized = true;
> +
> +	return 0;
> +
> +err_napi_del:
> +	netif_napi_del(&wwan_ptr->napi);
> +	free_netdev(dev);
> +err_clear_ctx:
> +	memset(&rmnet_ipa_ctx_struct, 0,
> sizeof(rmnet_ipa_ctx_struct));
> +
> +	return ret;
> +}
> +
> +static int ipa_wwan_remove(struct platform_device *pdev)
> +{
> +	struct ipa_wwan_private *wwan_ptr =
> netdev_priv(rmnet_ipa_ctx->dev);
> +
> +	dev_info(&pdev->dev, "rmnet_ipa started
> deinitialization\n");
> +
> +	mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	ipa_client_add();
> +
> +	if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) {
> +		ipa_ep_teardown(rmnet_ipa_ctx->wan_cons_ep_id);
> +		rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD;
> +	}
> +
> +	if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) {
> +		ipa_ep_teardown(rmnet_ipa_ctx->wan_prod_ep_id);
> +		rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD;
> +	}
> +
> +	ipa_client_remove();
> +
> +	netif_napi_del(&wwan_ptr->napi);
> +	mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex);
> +	unregister_netdev(rmnet_ipa_ctx->dev);
> +
> +	if (rmnet_ipa_ctx->dev)
> +		free_netdev(rmnet_ipa_ctx->dev);
> +	rmnet_ipa_ctx->dev = NULL;
> +
> +	mutex_destroy(&rmnet_ipa_ctx->mux_id_mutex);
> +	mutex_destroy(&rmnet_ipa_ctx->ep_setup_mutex);
> +
> +	initialized = false;
> +
> +	dev_info(&pdev->dev, "rmnet_ipa completed
> deinitialization\n");
> +
> +	return 0;
> +}
> +
> +/** rmnet_ipa_ap_suspend() - suspend callback for runtime_pm
> + * @dev: pointer to device
> + *
> + * This callback will be invoked by the runtime_pm framework when an
> AP suspend
> + * operation is invoked, usually by pressing a suspend button.
> + *
> + * Returns -EAGAIN to runtime_pm framework in case there are pending
> packets
> + * in the Tx queue. This will postpone the suspend operation until
> all the
> + * pending packets will be transmitted.
> + *
> + * In case there are no packets to send, releases the WWAN0_PROD
> entity.
> + * As an outcome, the number of IPA active clients should be
> decremented
> + * until IPA clocks can be gated.
> + */
> +static int rmnet_ipa_ap_suspend(struct device *dev)
> +{
> +	struct net_device *netdev = rmnet_ipa_ctx->dev;
> +	struct ipa_wwan_private *wwan_ptr;
> +	int ret;
> +
> +	if (!netdev) {
> +		ipa_err("netdev is NULL.\n");
> +		ret = 0;
> +		goto bail;
> +	}
> +
> +	netif_tx_lock_bh(netdev);
> +	wwan_ptr = netdev_priv(netdev);
> +	if (!wwan_ptr) {
> +		ipa_err("wwan_ptr is NULL.\n");
> +		ret = 0;
> +		goto unlock_and_bail;
> +	}
> +
> +	/* Do not allow A7 to suspend in case there are outstanding
> packets */
> +	if (atomic_read(&wwan_ptr->outstanding_pkts) != 0) {
> +		ret = -EAGAIN;
> +		goto unlock_and_bail;
> +	}
> +
> +	/* Make sure that there is no Tx operation ongoing */
> +	netif_stop_queue(netdev);
> +
> +	ret = 0;
> +	ipa_client_remove();
> +
> +unlock_and_bail:
> +	netif_tx_unlock_bh(netdev);
> +bail:
> +
> +	return ret;
> +}
> +
> +/** rmnet_ipa_ap_resume() - resume callback for runtime_pm
> + * @dev: pointer to device
> + *
> + * This callback will be invoked by the runtime_pm framework when an
> AP resume
> + * operation is invoked.
> + *
> + * Enables the network interface queue and returns success to the
> + * runtime_pm framework.
> + */
> +static int rmnet_ipa_ap_resume(struct device *dev)
> +{
> +	struct net_device *netdev = rmnet_ipa_ctx->dev;
> +
> +	ipa_client_add();
> +	if (netdev)
> +		netif_wake_queue(netdev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id rmnet_ipa_dt_match[] = {
> +	{.compatible = "qcom,rmnet-ipa"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, rmnet_ipa_dt_match);
> +
> +static const struct dev_pm_ops rmnet_ipa_pm_ops = {
> +	.suspend_noirq = rmnet_ipa_ap_suspend,
> +	.resume_noirq = rmnet_ipa_ap_resume,
> +};
> +
> +static struct platform_driver rmnet_ipa_driver = {
> +	.driver = {
> +		.name = "rmnet_ipa",
> +		.owner = THIS_MODULE,
> +		.pm = &rmnet_ipa_pm_ops,
> +		.of_match_table = rmnet_ipa_dt_match,
> +	},
> +	.probe = ipa_wwan_probe,
> +	.remove = ipa_wwan_remove,
> +};
> +
> +int ipa_wwan_init(void)
> +{
> +	if (initialized)
> +		return 0;
> +
> +	return platform_driver_register(&rmnet_ipa_driver);
> +}
> +
> +void ipa_wwan_cleanup(void)
> +{
> +	platform_driver_unregister(&rmnet_ipa_driver);
> +	memset(&rmnet_ipa_ctx_struct, 0,
> sizeof(rmnet_ipa_ctx_struct));
> +}
> +
> +static int ipa_rmnet_poll(struct napi_struct *napi, int budget)
> +{
> +	return ipa_rx_poll(rmnet_ipa_ctx->wan_cons_ep_id, budget);
> +}
> +
> +MODULE_DESCRIPTION("WWAN Network Interface");
> +MODULE_LICENSE("GPL v2");

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 00/12] net: introduce Qualcomm IPA driver
  2018-11-07  0:32 ` Alex Elder
@ 2018-11-07 15:46   ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 15:46 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Rob Herring,
	Mark Rutland, Networking, DTML, linux-arm-msm, linux-soc,
	Linux ARM, Linux Kernel Mailing List, syadagir, mjavid

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> The code has undergone considerable rework to prepare it for
> incorporation into upstream Linux.  Parts of it bear little
> resemblance to the original driver.  Still, some work remains
> to be done.  The current code and its design had a preliminary
> review, and some changes to the data path implementation were
> recommended.   These have not yet been addressed:
> - Use NAPI for all interfaces, not just RX (and WAN data) endpoints.
> - Do more work in the NAPI poll function, including collecting
>   completed TX requests and posting buffers for RX.
> - Do not use periodic NOP requests as a way to avoid TX interrupts.
> - The NAPI context should be associated with the hardware interrupt
>   (it is now associated with something abstracted from the hardware).
> - Use threaded interrupts, to avoid the need for using spinlocks and
>   atomic variables for synchronizing between workqueue and interrupt
>   context.
> - Have runtime power management enable and disable IPA clock and
>   interconnects.
> Many thanks to Arnd Bergmann, Ilias Apalodimas, and Bjorn Andersson
> for their early feedback.

Thanks for getting the current version out even with the long TODO
list. I've had my first deeper look at some of the patches and found
a few more things that likely require substantial rework. I also think
there is still significant room for simplifying it further, and getting
better performance out of it in the process.

Also, despite the criticism in my patch review, I have to say you've
done a great job at cutting out a lot of the things that were present
in the past, it's good to see that you have come this far with the
cleanup!

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 00/12] net: introduce Qualcomm IPA driver
@ 2018-11-07 15:46   ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-07 15:46 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> The code has undergone considerable rework to prepare it for
> incorporation into upstream Linux.  Parts of it bear little
> resemblance to the original driver.  Still, some work remains
> to be done.  The current code and its design had a preliminary
> review, and some changes to the data path implementation were
> recommended.   These have not yet been addressed:
> - Use NAPI for all interfaces, not just RX (and WAN data) endpoints.
> - Do more work in the NAPI poll function, including collecting
>   completed TX requests and posting buffers for RX.
> - Do not use periodic NOP requests as a way to avoid TX interrupts.
> - The NAPI context should be associated with the hardware interrupt
>   (it is now associated with something abstracted from the hardware).
> - Use threaded interrupts, to avoid the need for using spinlocks and
>   atomic variables for synchronizing between workqueue and interrupt
>   context.
> - Have runtime power management enable and disable IPA clock and
>   interconnects.
> Many thanks to Arnd Bergmann, Ilias Apalodimas, and Bjorn Andersson
> for their early feedback.

Thanks for getting the current version out even with the long TODO
list. I've had my first deeper look at some of the patches and found
a few more things that likely require substantial rework. I also think
there is still significant room for simplifying it further, and getting
better performance out of it in the process.

Also, despite the criticism in my patch review, I have to say you've
done a great job at cutting out a lot of the things that were present
in the past, it's good to see that you have come this far with the
cleanup!

      Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
  2018-11-07  0:40     ` Randy Dunlap
@ 2018-11-08 16:22       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-08 16:22 UTC (permalink / raw)
  To: Randy Dunlap, davem, arnd, bjorn.andersson, ilias.apalodimas
  Cc: netdev, devicetree, linux-arm-msm, linux-soc, linux-arm-kernel,
	linux-kernel, syadagir, mjavid, robh+dt, mark.rutland

On 11/6/18 6:40 PM, Randy Dunlap wrote:
> Hi-

Thanks Randy, I've fixed this in my own tree.	-Alex

> 
> On 11/6/18 4:32 PM, Alex Elder wrote:
>> diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
>> new file mode 100644
>> index 000000000000..f8ea9363f532
>> --- /dev/null
>> +++ b/drivers/net/ipa/Kconfig
>> @@ -0,0 +1,30 @@
>> +config IPA
>> +	tristate "Qualcomm IPA support"
>> +	depends on NET
>> +	select QCOM_QMI_HELPERS
>> +	select QCOM_MDT_LOADER
>> +	default n
>> +	help
>> +	  Choose Y here to include support for the Qualcomm IP
>> +	  Accelerator (IPA), a hardware block present in some
>> +	  Qualcomm SoCs.  The IPA is a programmable protocol
>> +	  processor that is capable of generic hardware handling
>> +	  of IP packets, including routing, filtering, and NAT.
>> +	  Currently the IPA driver supports only basic transport
>> +	  of network traffic between the AP and modem, on the
>> +	  Qualcomm SDM845 SoC.
>> +
>> +	  If unsure, say N.
>> +
>> +config IPA_ASSERT
>> +	bool "Enable IPA assertions"
>> +	depends on IPA
>> +	default y
>> +	help
>> +	 Incorporate IPA assertion verification in the build.  This
>> +	 cause various design assumptions to be checked at runtime,
> 
> 	 causes
> 
>> +	 generating a report (and a crash) if any assumed condition
>> +	 does not hold.  You may wish to disable this to avoid the
>> +	 overhead of checking.
>> +
>> +	 If unsure doubt, say "Y" here.
> 
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h"
@ 2018-11-08 16:22       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-08 16:22 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/6/18 6:40 PM, Randy Dunlap wrote:
> Hi-

Thanks Randy, I've fixed this in my own tree.	-Alex

> 
> On 11/6/18 4:32 PM, Alex Elder wrote:
>> diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig
>> new file mode 100644
>> index 000000000000..f8ea9363f532
>> --- /dev/null
>> +++ b/drivers/net/ipa/Kconfig
>> @@ -0,0 +1,30 @@
>> +config IPA
>> +	tristate "Qualcomm IPA support"
>> +	depends on NET
>> +	select QCOM_QMI_HELPERS
>> +	select QCOM_MDT_LOADER
>> +	default n
>> +	help
>> +	  Choose Y here to include support for the Qualcomm IP
>> +	  Accelerator (IPA), a hardware block present in some
>> +	  Qualcomm SoCs.  The IPA is a programmable protocol
>> +	  processor that is capable of generic hardware handling
>> +	  of IP packets, including routing, filtering, and NAT.
>> +	  Currently the IPA driver supports only basic transport
>> +	  of network traffic between the AP and modem, on the
>> +	  Qualcomm SDM845 SoC.
>> +
>> +	  If unsure, say N.
>> +
>> +config IPA_ASSERT
>> +	bool "Enable IPA assertions"
>> +	depends on IPA
>> +	default y
>> +	help
>> +	 Incorporate IPA assertion verification in the build.  This
>> +	 cause various design assumptions to be checked at runtime,
> 
> 	 causes
> 
>> +	 generating a report (and a crash) if any assumed condition
>> +	 does not hold.  You may wish to disable this to avoid the
>> +	 overhead of checking.
>> +
>> +	 If unsure doubt, say "Y" here.
> 
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-07 11:50     ` Arnd Bergmann
@ 2018-11-09 22:38       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-09 22:38 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Rob Herring, Mark Rutland, David Miller, Bjorn Andersson,
	Ilias Apalodimas, Networking, DTML, linux-arm-msm, linux-soc,
	Linux ARM, Linux Kernel Mailing List, syadagir, mjavid

On 11/7/18 5:50 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
>> device tree nodes.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>
>> ---
>>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>>  2 files changed, 151 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> 
> I think this should go into bindings/net instead of bindings/soc, since it's
> mostly about networking rather than a specific detail of managing the SoC
> itself.

Done (in my tree--and will be reflected next time I send something out).

>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> new file mode 100644
>> index 000000000000..d4d3d37df029
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> @@ -0,0 +1,136 @@
>> +Qualcomm IPA (IP Accelerator) Driver
>> +
>> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
>> +certain network processing tasks (e.g. filtering, routing, and NAT) from
>> +the main processor.  The IPA currently serves only as a network interface,
>> +providing access to an LTE network available via a modem.
> 
> That doesn't belong into the binding. Say what the hardware can do here,
> not what a specific implementation of the driver does at this moment.
> The binding should be written in an OS independent way after all.

OK.

>> +- interrupts-extended:
>> +       Specifies the IRQs used by the IPA.  Four cells are required,
>> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
>> +       from the modem; and the "ready for stage 2 initialization"
>> +       interrupt from the modem.  The first two are hardware IRQs; the
>> +       third and fourth are SMP2P input interrupts.
> 
> You mean 'four interrupts', not 'four cells' -- each interrupt specifier
> already consists of at least two cells (one for the phandle to the
> irqchip, plus one or more cells to describe that interrupt).

OK.

>> +- interconnects:
>> +       Specifies the interconnects used by the IPA.  Three cells are
>> +       required, specifying:  the path from the IPA to memory; from
>> +       IPA to internal (SoC resident) memory; and between the AP
>> +       subsystem and IPA for register access.
> 
> Same here and in the rest.

OK, I've fixed all of these.  Thank you.

					-Alex

> 
>       Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-09 22:38       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-09 22:38 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 5:50 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
>> device tree nodes.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>
>> ---
>>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>>  2 files changed, 151 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> 
> I think this should go into bindings/net instead of bindings/soc, since it's
> mostly about networking rather than a specific detail of managing the SoC
> itself.

Done (in my tree--and will be reflected next time I send something out).

>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> new file mode 100644
>> index 000000000000..d4d3d37df029
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> @@ -0,0 +1,136 @@
>> +Qualcomm IPA (IP Accelerator) Driver
>> +
>> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
>> +certain network processing tasks (e.g. filtering, routing, and NAT) from
>> +the main processor.  The IPA currently serves only as a network interface,
>> +providing access to an LTE network available via a modem.
> 
> That doesn't belong into the binding. Say what the hardware can do here,
> not what a specific implementation of the driver does at this moment.
> The binding should be written in an OS independent way after all.

OK.

>> +- interrupts-extended:
>> +       Specifies the IRQs used by the IPA.  Four cells are required,
>> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
>> +       from the modem; and the "ready for stage 2 initialization"
>> +       interrupt from the modem.  The first two are hardware IRQs; the
>> +       third and fourth are SMP2P input interrupts.
> 
> You mean 'four interrupts', not 'four cells' -- each interrupt specifier
> already consists of at least two cells (one for the phandle to the
> irqchip, plus one or more cells to describe that interrupt).

OK.

>> +- interconnects:
>> +       Specifies the interconnects used by the IPA.  Three cells are
>> +       required, specifying:  the path from the IPA to memory; from
>> +       IPA to internal (SoC resident) memory; and between the AP
>> +       subsystem and IPA for register access.
> 
> Same here and in the rest.

OK, I've fixed all of these.  Thank you.

					-Alex

> 
>       Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-07 14:59     ` Rob Herring
@ 2018-11-09 22:38       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-09 22:38 UTC (permalink / raw)
  To: Rob Herring
  Cc: Rob Herring, Mark Rutland, davem, Arnd Bergmann, Bjorn Andersson,
	ilias.apalodimas, netdev, devicetree, linux-arm-msm, linux-soc,
	linux-arm-kernel, Linux Kernel Mailing List, syadagir, mjavid

On 11/7/18 8:59 AM, Rob Herring wrote:
> On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
>>
>> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
>> device tree nodes.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>
>> ---
>>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>>  2 files changed, 151 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>>
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> new file mode 100644
>> index 000000000000..d4d3d37df029
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> @@ -0,0 +1,136 @@
>> +Qualcomm IPA (IP Accelerator) Driver
> 
> Bindings are for h/w not drivers.

OK.  I'll drop " Driver".

>> +
>> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
>> +certain network processing tasks (e.g. filtering, routing, and NAT) from
>> +the main processor.  The IPA currently serves only as a network interface,
>> +providing access to an LTE network available via a modem.
>> +
>> +The IPA sits between multiple independent "execution environments,"
>> +including the AP subsystem (APSS) and the modem.  The IPA presents
>> +a Generic Software Interface (GSI) to each execution environment.
>> +The GSI is an integral part of the IPA, but it is logically isolated
>> +and has a distinct interrupt and a separately-defined address space.
>> +
>> +    ----------   -------------   ---------
>> +    |        |   |G|       |G|   |       |
>> +    |  APSS  |===|S|  IPA  |S|===| Modem |
>> +    |        |   |I|       |I|   |       |
>> +    ----------   -------------   ---------
>> +
>> +See also:
>> +  bindings/interrupt-controller/interrupts.txt
>> +  bindings/interconnect/interconnect.txt
>> +  bindings/soc/qcom/qcom,smp2p.txt
>> +  bindings/reserved-memory/reserved-memory.txt
>> +  bindings/clock/clock-bindings.txt
>> +
>> +All properties defined below are required.
>> +
>> +- compatible:
>> +       Must be one of the following compatible strings:
>> +               "qcom,ipa-sdm845-modem_init"
>> +               "qcom,ipa-sdm845-tz_init"
> 
> Normal order is <vendor>,<soc>-<ipblock>."

I'll use "qcom,sdm845-ipa-modem-init" and "qcom,sdm845-ipa-tz-init".
(Or just "qcom,sdm845-ipa", depending on the outcome of the discussion
below.)

> Don't use '_'.

OK.

> What's the difference between these 2? It can't be detected somehow?

There is some early initialization, including loading some firmware,
that must be done by trusted code.  That can be done by either Trust
Zone or the modem.  If it's done by the modem, there is an additional
step required during initialization so the modem can tell the AP
that it has done its part, and the AP can finish IPA initialization.

There  is no way of detecting (e.g. by probing hardware) which is
in effect so we use DT.  I discussed this with Bjorn, who said that
this was a situation seen elsewhere and that using compatible strings
was the way he suggested to address it.

> This might be better expressed as a property. Then if Trustzone
> initializes things, it can just add a property.

A Boolean property to distinguish them would be fine as well, but
I would like to address this "common" problem consistently.

Bjorn, would you please weigh in?

>> +
>> +-reg:
>> +       Resources specyfing the physical address spaces of the IPA and GSI.
> 
> typo
> 
>> +
>> +-reg-names:
>> +       The names of the address space ranges defined by the "reg" property.
>> +       Must be "ipa" and "gsi".
>> +
>> +- interrupts-extended:
> 
> Use 'interrupts' here and describe what they are and the order. What
> they are connected to (and the need for interrupts-extended) is
> outside the scope of this binding.

I used interrupts-extended because there were two interrupt parents
(a "normal" interrupt controller and the interrupt controller implemented
for SMP2P input).  A paragraph here:
    bindings/interrupt-controller/interrupts.txt
recommends "interrupts-extended" in that case.

I have no objection to using just "interrupts" but can you tell me what
I misunderstood?  It seems like I need to do "interrupts-extended".

>> +       Specifies the IRQs used by the IPA.  Four cells are required,
>> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
>> +       from the modem; and the "ready for stage 2 initialization"
>> +       interrupt from the modem.  The first two are hardware IRQs; the
>> +       third and fourth are SMP2P input interrupts.
>> +
>> +- interrupt-names:
>> +       The names of the interrupts defined by the "interrupts-extended"
>> +       property.  Must be "ipa", "gsi", "ipa-clock-query", and
>> +       "ipa-post-init".
> 
> Format as one per line.

Done.  And I did this throughout the file where there was more than
one name.  One per line, no comma, no "and".

>> +
>> +- clocks:
>> +       Resource that defines the IPA core clock.
>> +
>> +- clock-names:
>> +       The name used for the IPA core clock.  Must be "core".
>> +
>> +- interconnects:
>> +       Specifies the interconnects used by the IPA.  Three cells are
>> +       required, specifying:  the path from the IPA to memory; from
>> +       IPA to internal (SoC resident) memory; and between the AP
>> +       subsystem and IPA for register access.
>> +
>> +- interconnect-names:
>> +       The names of the interconnects defined by the "interconnects"
>> +       property.  Must be "memory", "imem", and "config".
>> +
>> +- qcom,smem-states
>> +       The state bits used for SMP2P output.  Two cells must be specified.
>> +       The first indicates whether the value in the second bit is valid
>> +       (1 means valid).  The second, if valid, defines whether the IPA
>> +       clock is enabled (1 means enabled).
>> +
>> +- qcom,smem-state-names
>> +       The names of the state bits used for SMP2P output.  These must be
>> +       "ipa-clock-enabled-valid" and "ipa-clock-enabled".
>> +
>> +- memory-region
>> +       A phandle for a reserved memory area that holds the firmware passed
>> +       to Trust Zone for authentication.  (Note, this is required
>> +       only for "qcom,ipa-sdm845-tz_init".)
>> +
>> += EXAMPLE
>> +
>> +The following example represents the IPA present in the SDM845 SoC.  It
>> +shows portions of the "modem-smp2p" node to indicate its relationship
>> +with the interrupts and SMEM states used by the IPA.
>> +
>> +       modem-smp2p {
>> +               compatible = "qcom,smp2p";
>> +               . . .
>> +               ipa_smp2p_out: ipa-ap-to-modem {
>> +                       qcom,entry-name = "ipa";
>> +                       #qcom,smem-state-cells = <1>;
>> +               };
>> +
>> +               ipa_smp2p_in: ipa-modem-to-ap {
>> +                       qcom,entry-name = "ipa";
>> +                       interrupt-controller;
>> +                       #interrupt-cells = <2>;
>> +               };
>> +       };
>> +
>> +       ipa@1e00000 {
> 
> ipa@1e40000

Oops.  Fixed.

>> +               compatible = "qcom,ipa-sdm845-modem_init";
>> +
>> +               reg = <0x1e40000 0x34000>,
>> +                     <0x1e04000 0x2c000>;
>> +               reg-names = "ipa",
>> +                           "gsi";
>> +
>> +               interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
>> +                                     <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
>> +               interrupt-names = "ipa",
>> +                                 "gsi",
>> +                                 "ipa-clock-query",
>> +                                 "ipa-post-init";
>> +
>> +               clocks = <&rpmhcc RPMH_IPA_CLK>;
>> +               clock-names = "core";
>> +
>> +               interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
>> +                               <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
>> +                               <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
>> +               interconnect-names = "memory",
>> +                                    "imem",
>> +                                    "config";
>> +
>> +               qcom,smem-states = <&ipa_smp2p_out 0>,
>> +                                  <&ipa_smp2p_out 1>;
>> +               qcom,smem-state-names = "ipa-clock-enabled-valid",
>> +                                       "ipa-clock-enabled";
>> +       };
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> new file mode 100644
>> index 000000000000..3d0b2aabefc7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> @@ -0,0 +1,15 @@
>> +Qualcomm IPA RMNet Driver
>> +
>> +This binding describes the IPA RMNet driver, which is used to
>> +represent virtual interfaces available on the modem accessed via
>> +the IPA.  Other than the compatible string there are no properties
>> +associated with this device.
> 
> Only a compatible string is a sure sign this is not a h/w device and
> you are just abusing DT to instantiate drivers. Make the IPA driver
> instantiate any sub drivers it needs.

Yeah I have been thinking this but hadn't followed through on
doing anything about it yet.  I'll remove this node entirely.
It's possible it had other properties at one time, but in the
end this  represents a soft interface and can be implemented
within the IPA driver.

Thanks a lot for the review.

					-Alex
> 
> Rob
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-09 22:38       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-09 22:38 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 8:59 AM, Rob Herring wrote:
> On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
>>
>> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
>> device tree nodes.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>
>> ---
>>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>>  2 files changed, 151 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>>
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> new file mode 100644
>> index 000000000000..d4d3d37df029
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> @@ -0,0 +1,136 @@
>> +Qualcomm IPA (IP Accelerator) Driver
> 
> Bindings are for h/w not drivers.

OK.  I'll drop " Driver".

>> +
>> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
>> +certain network processing tasks (e.g. filtering, routing, and NAT) from
>> +the main processor.  The IPA currently serves only as a network interface,
>> +providing access to an LTE network available via a modem.
>> +
>> +The IPA sits between multiple independent "execution environments,"
>> +including the AP subsystem (APSS) and the modem.  The IPA presents
>> +a Generic Software Interface (GSI) to each execution environment.
>> +The GSI is an integral part of the IPA, but it is logically isolated
>> +and has a distinct interrupt and a separately-defined address space.
>> +
>> +    ----------   -------------   ---------
>> +    |        |   |G|       |G|   |       |
>> +    |  APSS  |===|S|  IPA  |S|===| Modem |
>> +    |        |   |I|       |I|   |       |
>> +    ----------   -------------   ---------
>> +
>> +See also:
>> +  bindings/interrupt-controller/interrupts.txt
>> +  bindings/interconnect/interconnect.txt
>> +  bindings/soc/qcom/qcom,smp2p.txt
>> +  bindings/reserved-memory/reserved-memory.txt
>> +  bindings/clock/clock-bindings.txt
>> +
>> +All properties defined below are required.
>> +
>> +- compatible:
>> +       Must be one of the following compatible strings:
>> +               "qcom,ipa-sdm845-modem_init"
>> +               "qcom,ipa-sdm845-tz_init"
> 
> Normal order is <vendor>,<soc>-<ipblock>."

I'll use "qcom,sdm845-ipa-modem-init" and "qcom,sdm845-ipa-tz-init".
(Or just "qcom,sdm845-ipa", depending on the outcome of the discussion
below.)

> Don't use '_'.

OK.

> What's the difference between these 2? It can't be detected somehow?

There is some early initialization, including loading some firmware,
that must be done by trusted code.  That can be done by either Trust
Zone or the modem.  If it's done by the modem, there is an additional
step required during initialization so the modem can tell the AP
that it has done its part, and the AP can finish IPA initialization.

There  is no way of detecting (e.g. by probing hardware) which is
in effect so we use DT.  I discussed this with Bjorn, who said that
this was a situation seen elsewhere and that using compatible strings
was the way he suggested to address it.

> This might be better expressed as a property. Then if Trustzone
> initializes things, it can just add a property.

A Boolean property to distinguish them would be fine as well, but
I would like to address this "common" problem consistently.

Bjorn, would you please weigh in?

>> +
>> +-reg:
>> +       Resources specyfing the physical address spaces of the IPA and GSI.
> 
> typo
> 
>> +
>> +-reg-names:
>> +       The names of the address space ranges defined by the "reg" property.
>> +       Must be "ipa" and "gsi".
>> +
>> +- interrupts-extended:
> 
> Use 'interrupts' here and describe what they are and the order. What
> they are connected to (and the need for interrupts-extended) is
> outside the scope of this binding.

I used interrupts-extended because there were two interrupt parents
(a "normal" interrupt controller and the interrupt controller implemented
for SMP2P input).  A paragraph here:
    bindings/interrupt-controller/interrupts.txt
recommends "interrupts-extended" in that case.

I have no objection to using just "interrupts" but can you tell me what
I misunderstood?  It seems like I need to do "interrupts-extended".

>> +       Specifies the IRQs used by the IPA.  Four cells are required,
>> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
>> +       from the modem; and the "ready for stage 2 initialization"
>> +       interrupt from the modem.  The first two are hardware IRQs; the
>> +       third and fourth are SMP2P input interrupts.
>> +
>> +- interrupt-names:
>> +       The names of the interrupts defined by the "interrupts-extended"
>> +       property.  Must be "ipa", "gsi", "ipa-clock-query", and
>> +       "ipa-post-init".
> 
> Format as one per line.

Done.  And I did this throughout the file where there was more than
one name.  One per line, no comma, no "and".

>> +
>> +- clocks:
>> +       Resource that defines the IPA core clock.
>> +
>> +- clock-names:
>> +       The name used for the IPA core clock.  Must be "core".
>> +
>> +- interconnects:
>> +       Specifies the interconnects used by the IPA.  Three cells are
>> +       required, specifying:  the path from the IPA to memory; from
>> +       IPA to internal (SoC resident) memory; and between the AP
>> +       subsystem and IPA for register access.
>> +
>> +- interconnect-names:
>> +       The names of the interconnects defined by the "interconnects"
>> +       property.  Must be "memory", "imem", and "config".
>> +
>> +- qcom,smem-states
>> +       The state bits used for SMP2P output.  Two cells must be specified.
>> +       The first indicates whether the value in the second bit is valid
>> +       (1 means valid).  The second, if valid, defines whether the IPA
>> +       clock is enabled (1 means enabled).
>> +
>> +- qcom,smem-state-names
>> +       The names of the state bits used for SMP2P output.  These must be
>> +       "ipa-clock-enabled-valid" and "ipa-clock-enabled".
>> +
>> +- memory-region
>> +       A phandle for a reserved memory area that holds the firmware passed
>> +       to Trust Zone for authentication.  (Note, this is required
>> +       only for "qcom,ipa-sdm845-tz_init".)
>> +
>> += EXAMPLE
>> +
>> +The following example represents the IPA present in the SDM845 SoC.  It
>> +shows portions of the "modem-smp2p" node to indicate its relationship
>> +with the interrupts and SMEM states used by the IPA.
>> +
>> +       modem-smp2p {
>> +               compatible = "qcom,smp2p";
>> +               . . .
>> +               ipa_smp2p_out: ipa-ap-to-modem {
>> +                       qcom,entry-name = "ipa";
>> +                       #qcom,smem-state-cells = <1>;
>> +               };
>> +
>> +               ipa_smp2p_in: ipa-modem-to-ap {
>> +                       qcom,entry-name = "ipa";
>> +                       interrupt-controller;
>> +                       #interrupt-cells = <2>;
>> +               };
>> +       };
>> +
>> +       ipa at 1e00000 {
> 
> ipa at 1e40000

Oops.  Fixed.

>> +               compatible = "qcom,ipa-sdm845-modem_init";
>> +
>> +               reg = <0x1e40000 0x34000>,
>> +                     <0x1e04000 0x2c000>;
>> +               reg-names = "ipa",
>> +                           "gsi";
>> +
>> +               interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
>> +                                     <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
>> +               interrupt-names = "ipa",
>> +                                 "gsi",
>> +                                 "ipa-clock-query",
>> +                                 "ipa-post-init";
>> +
>> +               clocks = <&rpmhcc RPMH_IPA_CLK>;
>> +               clock-names = "core";
>> +
>> +               interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
>> +                               <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
>> +                               <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
>> +               interconnect-names = "memory",
>> +                                    "imem",
>> +                                    "config";
>> +
>> +               qcom,smem-states = <&ipa_smp2p_out 0>,
>> +                                  <&ipa_smp2p_out 1>;
>> +               qcom,smem-state-names = "ipa-clock-enabled-valid",
>> +                                       "ipa-clock-enabled";
>> +       };
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> new file mode 100644
>> index 000000000000..3d0b2aabefc7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> @@ -0,0 +1,15 @@
>> +Qualcomm IPA RMNet Driver
>> +
>> +This binding describes the IPA RMNet driver, which is used to
>> +represent virtual interfaces available on the modem accessed via
>> +the IPA.  Other than the compatible string there are no properties
>> +associated with this device.
> 
> Only a compatible string is a sure sign this is not a h/w device and
> you are just abusing DT to instantiate drivers. Make the IPA driver
> instantiate any sub drivers it needs.

Yeah I have been thinking this but hadn't followed through on
doing anything about it yet.  I'll remove this node entirely.
It's possible it had other properties at one time, but in the
end this  represents a soft interface and can be implemented
within the IPA driver.

Thanks a lot for the review.

					-Alex
> 
> Rob
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-09 22:38       ` Alex Elder
  (?)
@ 2018-11-11  1:40         ` Rob Herring
  -1 siblings, 0 replies; 76+ messages in thread
From: Rob Herring @ 2018-11-11  1:40 UTC (permalink / raw)
  To: Alex Elder
  Cc: Mark Rutland, David Miller, Arnd Bergmann, Bjorn Andersson,
	ilias.apalodimas, netdev, devicetree, linux-arm-msm,
	open list:ARM/QUALCOMM SUPPORT,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	linux-kernel, syadagir, mjavid

On Fri, Nov 9, 2018 at 4:38 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 8:59 AM, Rob Herring wrote:
> > On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
> >>
> >> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> >> device tree nodes.
> >>
> >> Signed-off-by: Alex Elder <elder@linaro.org>
> >> ---
> >>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
> >>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
> >>  2 files changed, 151 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> >>
> >> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >> new file mode 100644
> >> index 000000000000..d4d3d37df029
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >> @@ -0,0 +1,136 @@
> >> +Qualcomm IPA (IP Accelerator) Driver
> >
> > Bindings are for h/w not drivers.
>
> OK.  I'll drop " Driver".
>
> >> +
> >> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> >> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> >> +the main processor.  The IPA currently serves only as a network interface,
> >> +providing access to an LTE network available via a modem.
> >> +
> >> +The IPA sits between multiple independent "execution environments,"
> >> +including the AP subsystem (APSS) and the modem.  The IPA presents
> >> +a Generic Software Interface (GSI) to each execution environment.
> >> +The GSI is an integral part of the IPA, but it is logically isolated
> >> +and has a distinct interrupt and a separately-defined address space.
> >> +
> >> +    ----------   -------------   ---------
> >> +    |        |   |G|       |G|   |       |
> >> +    |  APSS  |===|S|  IPA  |S|===| Modem |
> >> +    |        |   |I|       |I|   |       |
> >> +    ----------   -------------   ---------
> >> +
> >> +See also:
> >> +  bindings/interrupt-controller/interrupts.txt
> >> +  bindings/interconnect/interconnect.txt
> >> +  bindings/soc/qcom/qcom,smp2p.txt
> >> +  bindings/reserved-memory/reserved-memory.txt
> >> +  bindings/clock/clock-bindings.txt
> >> +
> >> +All properties defined below are required.
> >> +
> >> +- compatible:
> >> +       Must be one of the following compatible strings:
> >> +               "qcom,ipa-sdm845-modem_init"
> >> +               "qcom,ipa-sdm845-tz_init"
> >
> > Normal order is <vendor>,<soc>-<ipblock>."
>
> I'll use "qcom,sdm845-ipa-modem-init" and "qcom,sdm845-ipa-tz-init".
> (Or just "qcom,sdm845-ipa", depending on the outcome of the discussion
> below.)
>
> > Don't use '_'.
>
> OK.
>
> > What's the difference between these 2? It can't be detected somehow?
>
> There is some early initialization, including loading some firmware,
> that must be done by trusted code.  That can be done by either Trust
> Zone or the modem.  If it's done by the modem, there is an additional
> step required during initialization so the modem can tell the AP
> that it has done its part, and the AP can finish IPA initialization.
>
> There  is no way of detecting (e.g. by probing hardware) which is
> in effect so we use DT.  I discussed this with Bjorn, who said that
> this was a situation seen elsewhere and that using compatible strings
> was the way he suggested to address it.

Okay. However, if this is common for QCom blocks maybe we should
reconsider. I think compatible makes sense if the programming model
changes.

> > This might be better expressed as a property. Then if Trustzone
> > initializes things, it can just add a property.
>
> A Boolean property to distinguish them would be fine as well, but
> I would like to address this "common" problem consistently.
>
> Bjorn, would you please weigh in?
>
> >> +
> >> +-reg:
> >> +       Resources specyfing the physical address spaces of the IPA and GSI.
> >
> > typo
> >
> >> +
> >> +-reg-names:
> >> +       The names of the address space ranges defined by the "reg" property.
> >> +       Must be "ipa" and "gsi".
> >> +
> >> +- interrupts-extended:
> >
> > Use 'interrupts' here and describe what they are and the order. What
> > they are connected to (and the need for interrupts-extended) is
> > outside the scope of this binding.
>
> I used interrupts-extended because there were two interrupt parents
> (a "normal" interrupt controller and the interrupt controller implemented
> for SMP2P input).  A paragraph here:
>     bindings/interrupt-controller/interrupts.txt
> recommends "interrupts-extended" in that case.
>
> I have no objection to using just "interrupts" but can you tell me what
> I misunderstood?  It seems like I need to do "interrupts-extended".

Yes, in the dts you should use interrupts-extended. However, for
documentation purposes that aspect is not important. So we just use
interrupts most everywhere.

Rob

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-11  1:40         ` Rob Herring
  0 siblings, 0 replies; 76+ messages in thread
From: Rob Herring @ 2018-11-11  1:40 UTC (permalink / raw)
  To: Alex Elder
  Cc: Mark Rutland, David Miller, Arnd Bergmann, Bjorn Andersson,
	ilias.apalodimas, netdev, devicetree, linux-arm-msm,
	open list:ARM/QUALCOMM SUPPORT,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	linux-kernel, syadagir, mjavid

On Fri, Nov 9, 2018 at 4:38 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 8:59 AM, Rob Herring wrote:
> > On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
> >>
> >> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> >> device tree nodes.
> >>
> >> Signed-off-by: Alex Elder <elder@linaro.org>
> >> ---
> >>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
> >>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
> >>  2 files changed, 151 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> >>
> >> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >> new file mode 100644
> >> index 000000000000..d4d3d37df029
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >> @@ -0,0 +1,136 @@
> >> +Qualcomm IPA (IP Accelerator) Driver
> >
> > Bindings are for h/w not drivers.
>
> OK.  I'll drop " Driver".
>
> >> +
> >> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> >> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> >> +the main processor.  The IPA currently serves only as a network interface,
> >> +providing access to an LTE network available via a modem.
> >> +
> >> +The IPA sits between multiple independent "execution environments,"
> >> +including the AP subsystem (APSS) and the modem.  The IPA presents
> >> +a Generic Software Interface (GSI) to each execution environment.
> >> +The GSI is an integral part of the IPA, but it is logically isolated
> >> +and has a distinct interrupt and a separately-defined address space.
> >> +
> >> +    ----------   -------------   ---------
> >> +    |        |   |G|       |G|   |       |
> >> +    |  APSS  |===|S|  IPA  |S|===| Modem |
> >> +    |        |   |I|       |I|   |       |
> >> +    ----------   -------------   ---------
> >> +
> >> +See also:
> >> +  bindings/interrupt-controller/interrupts.txt
> >> +  bindings/interconnect/interconnect.txt
> >> +  bindings/soc/qcom/qcom,smp2p.txt
> >> +  bindings/reserved-memory/reserved-memory.txt
> >> +  bindings/clock/clock-bindings.txt
> >> +
> >> +All properties defined below are required.
> >> +
> >> +- compatible:
> >> +       Must be one of the following compatible strings:
> >> +               "qcom,ipa-sdm845-modem_init"
> >> +               "qcom,ipa-sdm845-tz_init"
> >
> > Normal order is <vendor>,<soc>-<ipblock>."
>
> I'll use "qcom,sdm845-ipa-modem-init" and "qcom,sdm845-ipa-tz-init".
> (Or just "qcom,sdm845-ipa", depending on the outcome of the discussion
> below.)
>
> > Don't use '_'.
>
> OK.
>
> > What's the difference between these 2? It can't be detected somehow?
>
> There is some early initialization, including loading some firmware,
> that must be done by trusted code.  That can be done by either Trust
> Zone or the modem.  If it's done by the modem, there is an additional
> step required during initialization so the modem can tell the AP
> that it has done its part, and the AP can finish IPA initialization.
>
> There  is no way of detecting (e.g. by probing hardware) which is
> in effect so we use DT.  I discussed this with Bjorn, who said that
> this was a situation seen elsewhere and that using compatible strings
> was the way he suggested to address it.

Okay. However, if this is common for QCom blocks maybe we should
reconsider. I think compatible makes sense if the programming model
changes.

> > This might be better expressed as a property. Then if Trustzone
> > initializes things, it can just add a property.
>
> A Boolean property to distinguish them would be fine as well, but
> I would like to address this "common" problem consistently.
>
> Bjorn, would you please weigh in?
>
> >> +
> >> +-reg:
> >> +       Resources specyfing the physical address spaces of the IPA and GSI.
> >
> > typo
> >
> >> +
> >> +-reg-names:
> >> +       The names of the address space ranges defined by the "reg" property.
> >> +       Must be "ipa" and "gsi".
> >> +
> >> +- interrupts-extended:
> >
> > Use 'interrupts' here and describe what they are and the order. What
> > they are connected to (and the need for interrupts-extended) is
> > outside the scope of this binding.
>
> I used interrupts-extended because there were two interrupt parents
> (a "normal" interrupt controller and the interrupt controller implemented
> for SMP2P input).  A paragraph here:
>     bindings/interrupt-controller/interrupts.txt
> recommends "interrupts-extended" in that case.
>
> I have no objection to using just "interrupts" but can you tell me what
> I misunderstood?  It seems like I need to do "interrupts-extended".

Yes, in the dts you should use interrupts-extended. However, for
documentation purposes that aspect is not important. So we just use
interrupts most everywhere.

Rob

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-11  1:40         ` Rob Herring
  0 siblings, 0 replies; 76+ messages in thread
From: Rob Herring @ 2018-11-11  1:40 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Nov 9, 2018 at 4:38 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 8:59 AM, Rob Herring wrote:
> > On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
> >>
> >> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
> >> device tree nodes.
> >>
> >> Signed-off-by: Alex Elder <elder@linaro.org>
> >> ---
> >>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
> >>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
> >>  2 files changed, 151 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
> >>
> >> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >> new file mode 100644
> >> index 000000000000..d4d3d37df029
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
> >> @@ -0,0 +1,136 @@
> >> +Qualcomm IPA (IP Accelerator) Driver
> >
> > Bindings are for h/w not drivers.
>
> OK.  I'll drop " Driver".
>
> >> +
> >> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
> >> +certain network processing tasks (e.g. filtering, routing, and NAT) from
> >> +the main processor.  The IPA currently serves only as a network interface,
> >> +providing access to an LTE network available via a modem.
> >> +
> >> +The IPA sits between multiple independent "execution environments,"
> >> +including the AP subsystem (APSS) and the modem.  The IPA presents
> >> +a Generic Software Interface (GSI) to each execution environment.
> >> +The GSI is an integral part of the IPA, but it is logically isolated
> >> +and has a distinct interrupt and a separately-defined address space.
> >> +
> >> +    ----------   -------------   ---------
> >> +    |        |   |G|       |G|   |       |
> >> +    |  APSS  |===|S|  IPA  |S|===| Modem |
> >> +    |        |   |I|       |I|   |       |
> >> +    ----------   -------------   ---------
> >> +
> >> +See also:
> >> +  bindings/interrupt-controller/interrupts.txt
> >> +  bindings/interconnect/interconnect.txt
> >> +  bindings/soc/qcom/qcom,smp2p.txt
> >> +  bindings/reserved-memory/reserved-memory.txt
> >> +  bindings/clock/clock-bindings.txt
> >> +
> >> +All properties defined below are required.
> >> +
> >> +- compatible:
> >> +       Must be one of the following compatible strings:
> >> +               "qcom,ipa-sdm845-modem_init"
> >> +               "qcom,ipa-sdm845-tz_init"
> >
> > Normal order is <vendor>,<soc>-<ipblock>."
>
> I'll use "qcom,sdm845-ipa-modem-init" and "qcom,sdm845-ipa-tz-init".
> (Or just "qcom,sdm845-ipa", depending on the outcome of the discussion
> below.)
>
> > Don't use '_'.
>
> OK.
>
> > What's the difference between these 2? It can't be detected somehow?
>
> There is some early initialization, including loading some firmware,
> that must be done by trusted code.  That can be done by either Trust
> Zone or the modem.  If it's done by the modem, there is an additional
> step required during initialization so the modem can tell the AP
> that it has done its part, and the AP can finish IPA initialization.
>
> There  is no way of detecting (e.g. by probing hardware) which is
> in effect so we use DT.  I discussed this with Bjorn, who said that
> this was a situation seen elsewhere and that using compatible strings
> was the way he suggested to address it.

Okay. However, if this is common for QCom blocks maybe we should
reconsider. I think compatible makes sense if the programming model
changes.

> > This might be better expressed as a property. Then if Trustzone
> > initializes things, it can just add a property.
>
> A Boolean property to distinguish them would be fine as well, but
> I would like to address this "common" problem consistently.
>
> Bjorn, would you please weigh in?
>
> >> +
> >> +-reg:
> >> +       Resources specyfing the physical address spaces of the IPA and GSI.
> >
> > typo
> >
> >> +
> >> +-reg-names:
> >> +       The names of the address space ranges defined by the "reg" property.
> >> +       Must be "ipa" and "gsi".
> >> +
> >> +- interrupts-extended:
> >
> > Use 'interrupts' here and describe what they are and the order. What
> > they are connected to (and the need for interrupts-extended) is
> > outside the scope of this binding.
>
> I used interrupts-extended because there were two interrupt parents
> (a "normal" interrupt controller and the interrupt controller implemented
> for SMP2P input).  A paragraph here:
>     bindings/interrupt-controller/interrupts.txt
> recommends "interrupts-extended" in that case.
>
> I have no objection to using just "interrupts" but can you tell me what
> I misunderstood?  It seems like I need to do "interrupts-extended".

Yes, in the dts you should use interrupts-extended. However, for
documentation purposes that aspect is not important. So we just use
interrupts most everywhere.

Rob

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
  2018-11-07 14:59     ` Rob Herring
@ 2018-11-13 16:28       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-13 16:28 UTC (permalink / raw)
  To: Rob Herring
  Cc: Rob Herring, Mark Rutland, davem, Arnd Bergmann, Bjorn Andersson,
	ilias.apalodimas, netdev, devicetree, linux-arm-msm, linux-soc,
	linux-arm-kernel, Linux Kernel Mailing List, syadagir, mjavid

On 11/7/18 8:59 AM, Rob Herring wrote:
> On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
>>
>> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
>> device tree nodes.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>

Rob, I'm just following up to let you know that I have now addressed
all of your suggestions in my current code.

This includes the removal of the "qcom,rmnet-ipa" DT node.  The driver
that was set up to match that node is now gone from the code, and this
entity is no longer represented by a "real" device.  It implements a
network device that represents the modem, and that is now set up and
torn down as needed by the "main" IPA code.

The one thing that might need to be addressed is whether a Boolean
flag should be used rather than a different compatible string to
select whether the modem or TZ is responsible for GSI firmware load
and launch.  If you and Bjorn agree it should be done with a flag
instead I'll fix the binding and code accordingly.

					-Alex

>> ---
>>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>>  2 files changed, 151 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>>
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> new file mode 100644
>> index 000000000000..d4d3d37df029
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> @@ -0,0 +1,136 @@
>> +Qualcomm IPA (IP Accelerator) Driver
> 
> Bindings are for h/w not drivers.
> 
>> +
>> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
>> +certain network processing tasks (e.g. filtering, routing, and NAT) from
>> +the main processor.  The IPA currently serves only as a network interface,
>> +providing access to an LTE network available via a modem.
>> +
>> +The IPA sits between multiple independent "execution environments,"
>> +including the AP subsystem (APSS) and the modem.  The IPA presents
>> +a Generic Software Interface (GSI) to each execution environment.
>> +The GSI is an integral part of the IPA, but it is logically isolated
>> +and has a distinct interrupt and a separately-defined address space.
>> +
>> +    ----------   -------------   ---------
>> +    |        |   |G|       |G|   |       |
>> +    |  APSS  |===|S|  IPA  |S|===| Modem |
>> +    |        |   |I|       |I|   |       |
>> +    ----------   -------------   ---------
>> +
>> +See also:
>> +  bindings/interrupt-controller/interrupts.txt
>> +  bindings/interconnect/interconnect.txt
>> +  bindings/soc/qcom/qcom,smp2p.txt
>> +  bindings/reserved-memory/reserved-memory.txt
>> +  bindings/clock/clock-bindings.txt
>> +
>> +All properties defined below are required.
>> +
>> +- compatible:
>> +       Must be one of the following compatible strings:
>> +               "qcom,ipa-sdm845-modem_init"
>> +               "qcom,ipa-sdm845-tz_init"
> 
> Normal order is <vendor>,<soc>-<ipblock>.
> 
> Don't use '_'.
> 
> What's the difference between these 2? It can't be detected somehow?
> This might be better expressed as a property. Then if Trustzone
> initializes things, it can just add a property.
> 
>> +
>> +-reg:
>> +       Resources specyfing the physical address spaces of the IPA and GSI.
> 
> typo
> 
>> +
>> +-reg-names:
>> +       The names of the address space ranges defined by the "reg" property.
>> +       Must be "ipa" and "gsi".
>> +
>> +- interrupts-extended:
> 
> Use 'interrupts' here and describe what they are and the order. What
> they are connected to (and the need for interrupts-extended) is
> outside the scope of this binding.
> 
>> +       Specifies the IRQs used by the IPA.  Four cells are required,
>> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
>> +       from the modem; and the "ready for stage 2 initialization"
>> +       interrupt from the modem.  The first two are hardware IRQs; the
>> +       third and fourth are SMP2P input interrupts.
>> +
>> +- interrupt-names:
>> +       The names of the interrupts defined by the "interrupts-extended"
>> +       property.  Must be "ipa", "gsi", "ipa-clock-query", and
>> +       "ipa-post-init".
> 
> Format as one per line.
> 
>> +
>> +- clocks:
>> +       Resource that defines the IPA core clock.
>> +
>> +- clock-names:
>> +       The name used for the IPA core clock.  Must be "core".
>> +
>> +- interconnects:
>> +       Specifies the interconnects used by the IPA.  Three cells are
>> +       required, specifying:  the path from the IPA to memory; from
>> +       IPA to internal (SoC resident) memory; and between the AP
>> +       subsystem and IPA for register access.
>> +
>> +- interconnect-names:
>> +       The names of the interconnects defined by the "interconnects"
>> +       property.  Must be "memory", "imem", and "config".
>> +
>> +- qcom,smem-states
>> +       The state bits used for SMP2P output.  Two cells must be specified.
>> +       The first indicates whether the value in the second bit is valid
>> +       (1 means valid).  The second, if valid, defines whether the IPA
>> +       clock is enabled (1 means enabled).
>> +
>> +- qcom,smem-state-names
>> +       The names of the state bits used for SMP2P output.  These must be
>> +       "ipa-clock-enabled-valid" and "ipa-clock-enabled".
>> +
>> +- memory-region
>> +       A phandle for a reserved memory area that holds the firmware passed
>> +       to Trust Zone for authentication.  (Note, this is required
>> +       only for "qcom,ipa-sdm845-tz_init".)
>> +
>> += EXAMPLE
>> +
>> +The following example represents the IPA present in the SDM845 SoC.  It
>> +shows portions of the "modem-smp2p" node to indicate its relationship
>> +with the interrupts and SMEM states used by the IPA.
>> +
>> +       modem-smp2p {
>> +               compatible = "qcom,smp2p";
>> +               . . .
>> +               ipa_smp2p_out: ipa-ap-to-modem {
>> +                       qcom,entry-name = "ipa";
>> +                       #qcom,smem-state-cells = <1>;
>> +               };
>> +
>> +               ipa_smp2p_in: ipa-modem-to-ap {
>> +                       qcom,entry-name = "ipa";
>> +                       interrupt-controller;
>> +                       #interrupt-cells = <2>;
>> +               };
>> +       };
>> +
>> +       ipa@1e00000 {
> 
> ipa@1e40000
> 
>> +               compatible = "qcom,ipa-sdm845-modem_init";
>> +
>> +               reg = <0x1e40000 0x34000>,
>> +                     <0x1e04000 0x2c000>;
>> +               reg-names = "ipa",
>> +                           "gsi";
>> +
>> +               interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
>> +                                     <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
>> +               interrupt-names = "ipa",
>> +                                 "gsi",
>> +                                 "ipa-clock-query",
>> +                                 "ipa-post-init";
>> +
>> +               clocks = <&rpmhcc RPMH_IPA_CLK>;
>> +               clock-names = "core";
>> +
>> +               interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
>> +                               <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
>> +                               <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
>> +               interconnect-names = "memory",
>> +                                    "imem",
>> +                                    "config";
>> +
>> +               qcom,smem-states = <&ipa_smp2p_out 0>,
>> +                                  <&ipa_smp2p_out 1>;
>> +               qcom,smem-state-names = "ipa-clock-enabled-valid",
>> +                                       "ipa-clock-enabled";
>> +       };
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> new file mode 100644
>> index 000000000000..3d0b2aabefc7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> @@ -0,0 +1,15 @@
>> +Qualcomm IPA RMNet Driver
>> +
>> +This binding describes the IPA RMNet driver, which is used to
>> +represent virtual interfaces available on the modem accessed via
>> +the IPA.  Other than the compatible string there are no properties
>> +associated with this device.
> 
> Only a compatible string is a sure sign this is not a h/w device and
> you are just abusing DT to instantiate drivers. Make the IPA driver
> instantiate any sub drivers it needs.
> 
> Rob
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings
@ 2018-11-13 16:28       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-13 16:28 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 8:59 AM, Rob Herring wrote:
> On Tue, Nov 6, 2018 at 6:33 PM Alex Elder <elder@linaro.org> wrote:
>>
>> Add the binding definitions for the "qcom,ipa" and "qcom,rmnet-ipa"
>> device tree nodes.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>

Rob, I'm just following up to let you know that I have now addressed
all of your suggestions in my current code.

This includes the removal of the "qcom,rmnet-ipa" DT node.  The driver
that was set up to match that node is now gone from the code, and this
entity is no longer represented by a "real" device.  It implements a
network device that represents the modem, and that is now set up and
torn down as needed by the "main" IPA code.

The one thing that might need to be addressed is whether a Boolean
flag should be used rather than a different compatible string to
select whether the modem or TZ is responsible for GSI firmware load
and launch.  If you and Bjorn agree it should be done with a flag
instead I'll fix the binding and code accordingly.

					-Alex

>> ---
>>  .../devicetree/bindings/soc/qcom/qcom,ipa.txt | 136 ++++++++++++++++++
>>  .../bindings/soc/qcom/qcom,rmnet-ipa.txt      |  15 ++
>>  2 files changed, 151 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>>  create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>>
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> new file mode 100644
>> index 000000000000..d4d3d37df029
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,ipa.txt
>> @@ -0,0 +1,136 @@
>> +Qualcomm IPA (IP Accelerator) Driver
> 
> Bindings are for h/w not drivers.
> 
>> +
>> +This binding describes the Qualcomm IPA.  The IPA is capable of offloading
>> +certain network processing tasks (e.g. filtering, routing, and NAT) from
>> +the main processor.  The IPA currently serves only as a network interface,
>> +providing access to an LTE network available via a modem.
>> +
>> +The IPA sits between multiple independent "execution environments,"
>> +including the AP subsystem (APSS) and the modem.  The IPA presents
>> +a Generic Software Interface (GSI) to each execution environment.
>> +The GSI is an integral part of the IPA, but it is logically isolated
>> +and has a distinct interrupt and a separately-defined address space.
>> +
>> +    ----------   -------------   ---------
>> +    |        |   |G|       |G|   |       |
>> +    |  APSS  |===|S|  IPA  |S|===| Modem |
>> +    |        |   |I|       |I|   |       |
>> +    ----------   -------------   ---------
>> +
>> +See also:
>> +  bindings/interrupt-controller/interrupts.txt
>> +  bindings/interconnect/interconnect.txt
>> +  bindings/soc/qcom/qcom,smp2p.txt
>> +  bindings/reserved-memory/reserved-memory.txt
>> +  bindings/clock/clock-bindings.txt
>> +
>> +All properties defined below are required.
>> +
>> +- compatible:
>> +       Must be one of the following compatible strings:
>> +               "qcom,ipa-sdm845-modem_init"
>> +               "qcom,ipa-sdm845-tz_init"
> 
> Normal order is <vendor>,<soc>-<ipblock>.
> 
> Don't use '_'.
> 
> What's the difference between these 2? It can't be detected somehow?
> This might be better expressed as a property. Then if Trustzone
> initializes things, it can just add a property.
> 
>> +
>> +-reg:
>> +       Resources specyfing the physical address spaces of the IPA and GSI.
> 
> typo
> 
>> +
>> +-reg-names:
>> +       The names of the address space ranges defined by the "reg" property.
>> +       Must be "ipa" and "gsi".
>> +
>> +- interrupts-extended:
> 
> Use 'interrupts' here and describe what they are and the order. What
> they are connected to (and the need for interrupts-extended) is
> outside the scope of this binding.
> 
>> +       Specifies the IRQs used by the IPA.  Four cells are required,
>> +       specifying: the IPA IRQ; the GSI IRQ; the clock query interrupt
>> +       from the modem; and the "ready for stage 2 initialization"
>> +       interrupt from the modem.  The first two are hardware IRQs; the
>> +       third and fourth are SMP2P input interrupts.
>> +
>> +- interrupt-names:
>> +       The names of the interrupts defined by the "interrupts-extended"
>> +       property.  Must be "ipa", "gsi", "ipa-clock-query", and
>> +       "ipa-post-init".
> 
> Format as one per line.
> 
>> +
>> +- clocks:
>> +       Resource that defines the IPA core clock.
>> +
>> +- clock-names:
>> +       The name used for the IPA core clock.  Must be "core".
>> +
>> +- interconnects:
>> +       Specifies the interconnects used by the IPA.  Three cells are
>> +       required, specifying:  the path from the IPA to memory; from
>> +       IPA to internal (SoC resident) memory; and between the AP
>> +       subsystem and IPA for register access.
>> +
>> +- interconnect-names:
>> +       The names of the interconnects defined by the "interconnects"
>> +       property.  Must be "memory", "imem", and "config".
>> +
>> +- qcom,smem-states
>> +       The state bits used for SMP2P output.  Two cells must be specified.
>> +       The first indicates whether the value in the second bit is valid
>> +       (1 means valid).  The second, if valid, defines whether the IPA
>> +       clock is enabled (1 means enabled).
>> +
>> +- qcom,smem-state-names
>> +       The names of the state bits used for SMP2P output.  These must be
>> +       "ipa-clock-enabled-valid" and "ipa-clock-enabled".
>> +
>> +- memory-region
>> +       A phandle for a reserved memory area that holds the firmware passed
>> +       to Trust Zone for authentication.  (Note, this is required
>> +       only for "qcom,ipa-sdm845-tz_init".)
>> +
>> += EXAMPLE
>> +
>> +The following example represents the IPA present in the SDM845 SoC.  It
>> +shows portions of the "modem-smp2p" node to indicate its relationship
>> +with the interrupts and SMEM states used by the IPA.
>> +
>> +       modem-smp2p {
>> +               compatible = "qcom,smp2p";
>> +               . . .
>> +               ipa_smp2p_out: ipa-ap-to-modem {
>> +                       qcom,entry-name = "ipa";
>> +                       #qcom,smem-state-cells = <1>;
>> +               };
>> +
>> +               ipa_smp2p_in: ipa-modem-to-ap {
>> +                       qcom,entry-name = "ipa";
>> +                       interrupt-controller;
>> +                       #interrupt-cells = <2>;
>> +               };
>> +       };
>> +
>> +       ipa at 1e00000 {
> 
> ipa at 1e40000
> 
>> +               compatible = "qcom,ipa-sdm845-modem_init";
>> +
>> +               reg = <0x1e40000 0x34000>,
>> +                     <0x1e04000 0x2c000>;
>> +               reg-names = "ipa",
>> +                           "gsi";
>> +
>> +               interrupts-extended = <&intc 0 311 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&intc 0 432 IRQ_TYPE_LEVEL_HIGH>,
>> +                                     <&ipa_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
>> +                                     <&ipa_smp2p_in 1 IRQ_TYPE_EDGE_RISING>;
>> +               interrupt-names = "ipa",
>> +                                 "gsi",
>> +                                 "ipa-clock-query",
>> +                                 "ipa-post-init";
>> +
>> +               clocks = <&rpmhcc RPMH_IPA_CLK>;
>> +               clock-names = "core";
>> +
>> +               interconnects = <&qnoc MASTER_IPA &qnoc SLAVE_EBI1>,
>> +                               <&qnoc MASTER_IPA &qnoc SLAVE_IMEM>,
>> +                               <&qnoc MASTER_APPSS_PROC &qnoc SLAVE_IPA_CFG>;
>> +               interconnect-names = "memory",
>> +                                    "imem",
>> +                                    "config";
>> +
>> +               qcom,smem-states = <&ipa_smp2p_out 0>,
>> +                                  <&ipa_smp2p_out 1>;
>> +               qcom,smem-state-names = "ipa-clock-enabled-valid",
>> +                                       "ipa-clock-enabled";
>> +       };
>> diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> new file mode 100644
>> index 000000000000..3d0b2aabefc7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,rmnet-ipa.txt
>> @@ -0,0 +1,15 @@
>> +Qualcomm IPA RMNet Driver
>> +
>> +This binding describes the IPA RMNet driver, which is used to
>> +represent virtual interfaces available on the modem accessed via
>> +the IPA.  Other than the compatible string there are no properties
>> +associated with this device.
> 
> Only a compatible string is a sure sign this is not a h/w device and
> you are just abusing DT to instantiate drivers. Make the IPA driver
> instantiate any sub drivers it needs.
> 
> Rob
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers
  2018-11-07 12:17     ` Arnd Bergmann
@ 2018-11-13 16:33       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-13 16:33 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On 11/7/18 6:17 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> This patch includes code implementing the IPA DMA module, which
>> defines a structure to represent a DMA allocation for the IPA device.
>> It's used throughout the IPA code.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>
> 
> I looked through all the users of this and couldn't fine one that actually
> benefits from it. I'd say better drop this patch entirely and open-code
> the contents in the callers. That will help readability since the dma
> API is well understood by many people.

Originally this was done to make it more obvious that all DMA allocations
were done with the same device pointer.  Previously there were several
separate devices, and it wasn't very obvious that only one was used (and
required).  Now that we're past that it's not difficult to do as you suggest.

I have now done that, and in the process identified a few more ways to
improve/simplify the code.  The net result is that more lines of code
were removed than were present in "ipa_dma.[ch]".  I see that as a
win (aside from your point below).

> Generally speaking, try not to wrap Linux interfaces into driver specific
> helper functions.

Agreed.  Thanks a lot for your review.

					-Alex

> 
>       Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers
@ 2018-11-13 16:33       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-13 16:33 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 6:17 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> This patch includes code implementing the IPA DMA module, which
>> defines a structure to represent a DMA allocation for the IPA device.
>> It's used throughout the IPA code.
>>
>> Signed-off-by: Alex Elder <elder@linaro.org>
> 
> I looked through all the users of this and couldn't fine one that actually
> benefits from it. I'd say better drop this patch entirely and open-code
> the contents in the callers. That will help readability since the dma
> API is well understood by many people.

Originally this was done to make it more obvious that all DMA allocations
were done with the same device pointer.  Previously there were several
separate devices, and it wasn't very obvious that only one was used (and
required).  Now that we're past that it's not difficult to do as you suggest.

I have now done that, and in the process identified a few more ways to
improve/simplify the code.  The net result is that more lines of code
were removed than were present in "ipa_dma.[ch]".  I see that as a
win (aside from your point below).

> Generally speaking, try not to wrap Linux interfaces into driver specific
> helper functions.

Agreed.  Thanks a lot for your review.

					-Alex

> 
>       Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 04/12] soc: qcom: ipa: immediate commands
  2018-11-07 14:36     ` Arnd Bergmann
@ 2018-11-13 16:58       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-13 16:58 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On 11/7/18 8:36 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> +/**
>> + * struct ipahal_context - HAL global context data
>> + * @empty_fltrt_tbl:   Empty table to be used for table initialization
>> + */
>> +static struct ipahal_context {
>> +       struct ipa_dma_mem empty_fltrt_tbl;
>> +} ipahal_ctx_struct;
>> +static struct ipahal_context *ipahal_ctx = &ipahal_ctx_struct;
> 
> Remove the global variables here

Not done yet, but I will do this.  I've been working on eliminating
the top-level "ipa_ctx" global (which is *very* pervasive) and in
the process I'm eliminating all the others as well.  I'll get to
this soon.

>> +/* Immediate commands H/W structures */
>> +
>> +/* struct ipa_imm_cmd_hw_ip_fltrt_init - IP_V*_FILTER_INIT/IP_V*_ROUTING_INIT
>> + * command payload in H/W format.
>> + * Inits IPv4/v6 routing or filter block.
>> + * @hash_rules_addr: Addr in system mem where hashable flt/rt rules starts
>> + * @hash_rules_size: Size in bytes of the hashable tbl to cpy to local mem
>> + * @hash_local_addr: Addr in shared mem where hashable flt/rt tbl should
>> + *  be copied to
>> + * @nhash_rules_size: Size in bytes of the non-hashable tbl to cpy to local mem
>> + * @nhash_local_addr: Addr in shared mem where non-hashable flt/rt tbl should
>> + *  be copied to
>> + * @rsvd: reserved
>> + * @nhash_rules_addr: Addr in sys mem where non-hashable flt/rt tbl starts
>> + */
>> +struct ipa_imm_cmd_hw_ip_fltrt_init {
>> +       u64 hash_rules_addr;
>> +       u64 hash_rules_size     : 12,
>> +           hash_local_addr     : 16,
>> +           nhash_rules_size    : 12,
>> +           nhash_local_addr    : 16,
>> +           rsvd                : 8;
>> +       u64 nhash_rules_addr;
>> +};
> 
> In hardware structures, you should not use bit fields, as the ordering
> of the bits is not well-defined in C. The only portable way to do this
> is to use shifts and masks unfortunately.

This is something I held off fixing because I have seen other use
of bit fields in the kernel.  I wasn't sure whether my instinct about
it (which matches what you say) was wrong, and didn't want to do the
work to change things over to masks without knowing.  Based on your
suggestion, I will proceed with this conversion.

>> +struct ipa_imm_cmd_hw_hdr_init_local {
>> +       u64 hdr_table_addr;
>> +       u32 size_hdr_table      : 12,
>> +           hdr_addr            : 16,
>> +           rsvd                : 4;
>> +};
> 
> I would also add a 'u32 pad' member at the end to make the padding
> explicit here, or mark the first member as '__aligned(4) __packed'
> if you want to avoid the padding.

Yes, this is a good suggestion, and I will implement it.

You're right that the actual size of this structure includes the
extra 4 byte pad.  But I'm not actually sure whether the hardware
touches it because the size of immediate commands is implied by
the opcode.  To be safe, I'll make the pad explicit; but if I
learn it's not needed I'll define it to be packed.

>> +void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset)
>> +{
>> +       struct ipa_imm_cmd_hw_dma_shared_mem *data;
>> +
>> +       ipa_assert(mem->size < 1 << 16);        /* size is 16 bits wide */
>> +       ipa_assert(offset < 1 << 16);           /* local_addr is 16 bits wide */
>> +
>> +       data = kzalloc(sizeof(*data), GFP_KERNEL);
>> +       if (!data)
>> +               return NULL;
>> +
>> +       data->size = mem->size;
>> +       data->local_addr = offset;
>> +       data->direction = 0;    /* 0 = write to IPA; 1 = read from IPA */
>> +       data->skip_pipeline_clear = 0;
>> +       data->pipeline_clear_options = IPAHAL_HPS_CLEAR;
>> +       data->system_addr = mem->phys;
>> +
>> +       return data;
>> +}
> 
> The 'void *' return looks odd here, and also the dynamic allocation.

It was done because it allows the definition of the data structure
to be hidden within this file.

> It looks to me like all these functions could be better done the
> other way round, basically putting the
> ipa_imm_cmd_hw_dma_shared_mem etc structures on the stack
> of the caller. At least for this one, the dynamic allocation
> doesn't help at all because the caller is the same that
> frees it again after the command. I suspect the same is
> true for a lot of those commands.

Yes, I see what you're saying.  In fact, now that I look, all of
these payload allocating functions except for one are used just
the way you describe (freed in the same function that uses it).
And the one is saved with the intention of avoiding an allocation
failure...  But I'll mention that this code was structured very
differently originally.

So I agree, putting them on the stack (given they're relatively
small--most 16 bytes one 24 bytes) is better.  And it seems I
can reduce some complexity by getting rid of that preallocated
command, which is a great outcome.

If I run into trouble implementing any of the above suggestions
I will circle back and explain.

Thanks a lot.

					-Alex

> 
>        Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 04/12] soc: qcom: ipa: immediate commands
@ 2018-11-13 16:58       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-13 16:58 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 8:36 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> +/**
>> + * struct ipahal_context - HAL global context data
>> + * @empty_fltrt_tbl:   Empty table to be used for table initialization
>> + */
>> +static struct ipahal_context {
>> +       struct ipa_dma_mem empty_fltrt_tbl;
>> +} ipahal_ctx_struct;
>> +static struct ipahal_context *ipahal_ctx = &ipahal_ctx_struct;
> 
> Remove the global variables here

Not done yet, but I will do this.  I've been working on eliminating
the top-level "ipa_ctx" global (which is *very* pervasive) and in
the process I'm eliminating all the others as well.  I'll get to
this soon.

>> +/* Immediate commands H/W structures */
>> +
>> +/* struct ipa_imm_cmd_hw_ip_fltrt_init - IP_V*_FILTER_INIT/IP_V*_ROUTING_INIT
>> + * command payload in H/W format.
>> + * Inits IPv4/v6 routing or filter block.
>> + * @hash_rules_addr: Addr in system mem where hashable flt/rt rules starts
>> + * @hash_rules_size: Size in bytes of the hashable tbl to cpy to local mem
>> + * @hash_local_addr: Addr in shared mem where hashable flt/rt tbl should
>> + *  be copied to
>> + * @nhash_rules_size: Size in bytes of the non-hashable tbl to cpy to local mem
>> + * @nhash_local_addr: Addr in shared mem where non-hashable flt/rt tbl should
>> + *  be copied to
>> + * @rsvd: reserved
>> + * @nhash_rules_addr: Addr in sys mem where non-hashable flt/rt tbl starts
>> + */
>> +struct ipa_imm_cmd_hw_ip_fltrt_init {
>> +       u64 hash_rules_addr;
>> +       u64 hash_rules_size     : 12,
>> +           hash_local_addr     : 16,
>> +           nhash_rules_size    : 12,
>> +           nhash_local_addr    : 16,
>> +           rsvd                : 8;
>> +       u64 nhash_rules_addr;
>> +};
> 
> In hardware structures, you should not use bit fields, as the ordering
> of the bits is not well-defined in C. The only portable way to do this
> is to use shifts and masks unfortunately.

This is something I held off fixing because I have seen other use
of bit fields in the kernel.  I wasn't sure whether my instinct about
it (which matches what you say) was wrong, and didn't want to do the
work to change things over to masks without knowing.  Based on your
suggestion, I will proceed with this conversion.

>> +struct ipa_imm_cmd_hw_hdr_init_local {
>> +       u64 hdr_table_addr;
>> +       u32 size_hdr_table      : 12,
>> +           hdr_addr            : 16,
>> +           rsvd                : 4;
>> +};
> 
> I would also add a 'u32 pad' member at the end to make the padding
> explicit here, or mark the first member as '__aligned(4) __packed'
> if you want to avoid the padding.

Yes, this is a good suggestion, and I will implement it.

You're right that the actual size of this structure includes the
extra 4 byte pad.  But I'm not actually sure whether the hardware
touches it because the size of immediate commands is implied by
the opcode.  To be safe, I'll make the pad explicit; but if I
learn it's not needed I'll define it to be packed.

>> +void *ipahal_dma_shared_mem_write_pyld(struct ipa_dma_mem *mem, u32 offset)
>> +{
>> +       struct ipa_imm_cmd_hw_dma_shared_mem *data;
>> +
>> +       ipa_assert(mem->size < 1 << 16);        /* size is 16 bits wide */
>> +       ipa_assert(offset < 1 << 16);           /* local_addr is 16 bits wide */
>> +
>> +       data = kzalloc(sizeof(*data), GFP_KERNEL);
>> +       if (!data)
>> +               return NULL;
>> +
>> +       data->size = mem->size;
>> +       data->local_addr = offset;
>> +       data->direction = 0;    /* 0 = write to IPA; 1 = read from IPA */
>> +       data->skip_pipeline_clear = 0;
>> +       data->pipeline_clear_options = IPAHAL_HPS_CLEAR;
>> +       data->system_addr = mem->phys;
>> +
>> +       return data;
>> +}
> 
> The 'void *' return looks odd here, and also the dynamic allocation.

It was done because it allows the definition of the data structure
to be hidden within this file.

> It looks to me like all these functions could be better done the
> other way round, basically putting the
> ipa_imm_cmd_hw_dma_shared_mem etc structures on the stack
> of the caller. At least for this one, the dynamic allocation
> doesn't help at all because the caller is the same that
> frees it again after the command. I suspect the same is
> true for a lot of those commands.

Yes, I see what you're saying.  In fact, now that I look, all of
these payload allocating functions except for one are used just
the way you describe (freed in the same function that uses it).
And the one is saved with the intention of avoiding an allocation
failure...  But I'll mention that this code was structured very
differently originally.

So I agree, putting them on the stack (given they're relatively
small--most 16 bytes one 24 bytes) is better.  And it seems I
can reduce some complexity by getting rid of that preallocated
command, which is a great outcome.

If I run into trouble implementing any of the above suggestions
I will circle back and explain.

Thanks a lot.

					-Alex

> 
>        Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
  2018-11-07 15:00     ` Arnd Bergmann
@ 2018-11-15  2:48       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-15  2:48 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On 11/7/18 9:00 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
>> new file mode 100644
>> index 000000000000..5e0aa6163235
>> --- /dev/null
>> +++ b/drivers/net/ipa/ipa_reg.c
>> @@ -0,0 +1,972 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
>> + * Copyright (C) 2018 Linaro Ltd.
>> + */
>> +
>> +#include <linux/types.h>
>> +#include <linux/io.h>
>> +#include <linux/bitfield.h>
>> +
>> +#include "ipa_reg.h"
>> +
>> +/* I/O remapped base address of IPA register space */
>> +static void __iomem *ipa_reg_virt;
> 
> This should of course be part of the device structure.

Yes, this should have been be in that structure to begin with.

I'm working through doing a comprehensive replacement of
global variables like this with values passed around as
arguments.  I've intended to do it all along but your nudge
pushed it to the top of my list.  It's a *lot* of work, much
more than I realized.  But I'm making rapid progress.

>> +/* struct ipa_reg_desc - descriptor for an abstracted hardware register
>> + *
>> + * @construct - fn to construct the register value from its field structure
>> + * @parse - function to parse register field values into its field structure
>> + * @offset - register offset relative to base address
>> + * @n_ofst - size multiplier for "N-parameterized" registers
>> + */
>> +struct ipa_reg_desc {
>> +       u32 (*construct)(enum ipa_reg reg, const void *fields);
>> +       void (*parse)(enum ipa_reg reg, void *fields, u32 val);
>> +       u32 offset;
>> +       u16 n_ofst;
>> +};
> 
> Indirect function pointers can be a bit expensive in the post-spectre
> days. It's probably not overly important if these are always part of
> an MMIO access function, but you should be careful about using
> these in the data path.

OK.

There used to be a more elaborate scheme for supporting
lots of versions, and I have tried to preserve at least part
of the underlying mechanism (the parse and construct functions).
Not all of these registers use the indirect functions, and
it looks to me like none of them are in the data path.

The most important registers for the fast path are found in
the GSI code.  And that doesn't use this construct--it only
reads and writes 32-bit registers.  (I think it differs
because it was originally developed by a different team.)

> How many different versions do we have to support in practice
I don't know for sure how many versions really were used,
but the original code had about 10 distinct version values,
many of which shared most (but not all) register definitions
(offset and bit field widths) with older versions.

With the upstream code the decision was made to ignore anything
older than IPA version 3 (and 3.5.1 in particular, which is
found in the SDM845 SoC).  

It may be that this parse/construct mechanism isn't justified
at this point.  I thought the way it presented a generic
interface was useful, but with just one (initial) hardware
target we don't (yet) realize its potential benefit.  It could
be added back later, as support for new versions is added.

As of now I don't plan to change this, but if you or someone
else feels it would be better without it I can do that.

					-Alex


>        Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
@ 2018-11-15  2:48       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-15  2:48 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 9:00 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
>> new file mode 100644
>> index 000000000000..5e0aa6163235
>> --- /dev/null
>> +++ b/drivers/net/ipa/ipa_reg.c
>> @@ -0,0 +1,972 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
>> + * Copyright (C) 2018 Linaro Ltd.
>> + */
>> +
>> +#include <linux/types.h>
>> +#include <linux/io.h>
>> +#include <linux/bitfield.h>
>> +
>> +#include "ipa_reg.h"
>> +
>> +/* I/O remapped base address of IPA register space */
>> +static void __iomem *ipa_reg_virt;
> 
> This should of course be part of the device structure.

Yes, this should have been be in that structure to begin with.

I'm working through doing a comprehensive replacement of
global variables like this with values passed around as
arguments.  I've intended to do it all along but your nudge
pushed it to the top of my list.  It's a *lot* of work, much
more than I realized.  But I'm making rapid progress.

>> +/* struct ipa_reg_desc - descriptor for an abstracted hardware register
>> + *
>> + * @construct - fn to construct the register value from its field structure
>> + * @parse - function to parse register field values into its field structure
>> + * @offset - register offset relative to base address
>> + * @n_ofst - size multiplier for "N-parameterized" registers
>> + */
>> +struct ipa_reg_desc {
>> +       u32 (*construct)(enum ipa_reg reg, const void *fields);
>> +       void (*parse)(enum ipa_reg reg, void *fields, u32 val);
>> +       u32 offset;
>> +       u16 n_ofst;
>> +};
> 
> Indirect function pointers can be a bit expensive in the post-spectre
> days. It's probably not overly important if these are always part of
> an MMIO access function, but you should be careful about using
> these in the data path.

OK.

There used to be a more elaborate scheme for supporting
lots of versions, and I have tried to preserve at least part
of the underlying mechanism (the parse and construct functions).
Not all of these registers use the indirect functions, and
it looks to me like none of them are in the data path.

The most important registers for the fast path are found in
the GSI code.  And that doesn't use this construct--it only
reads and writes 32-bit registers.  (I think it differs
because it was originally developed by a different team.)

> How many different versions do we have to support in practice
I don't know for sure how many versions really were used,
but the original code had about 10 distinct version values,
many of which shared most (but not all) register definitions
(offset and bit field widths) with older versions.

With the upstream code the decision was made to ignore anything
older than IPA version 3 (and 3.5.1 in particular, which is
found in the SDM845 SoC).  

It may be that this parse/construct mechanism isn't justified
at this point.  I thought the way it presented a generic
interface was useful, but with just one (initial) hardware
target we don't (yet) realize its potential benefit.  It could
be added back later, as support for new versions is added.

As of now I don't plan to change this, but if you or someone
else feels it would be better without it I can do that.

					-Alex


>        Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file
  2018-11-07 14:08     ` Arnd Bergmann
@ 2018-11-15  3:11       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-15  3:11 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On 11/7/18 8:08 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> 
>> +static void ipa_client_remove_deferred(struct work_struct *work);
> 
> Try to avoid forward declarations by reordering the code in call order,
> it will also make it easier to read.
> 
>> +static DECLARE_WORK(ipa_client_remove_work, ipa_client_remove_deferred);

Done.  I've actually reworked this a lot, and pulled all the
clock (and interconnect) related code into a separate source file.
No more forward declarations (there anyway), and the work structure
is now embedded in the top-level IPA structure so I can derive
it again in the work function (rather than using the global).

>> +static struct ipa_context ipa_ctx_struct;
>> +struct ipa_context *ipa_ctx = &ipa_ctx_struct;
> 
> Global state variables should generally be removed as well, and
> passed around as function arguments.

Working on this.

>> +static int hdr_init_local_cmd(u32 offset, u32 size)
>> +{
>> +       struct ipa_desc desc = { };
>> +       struct ipa_dma_mem mem;
>> +       void *payload;
>> +       int ret;
>> +
>> +       if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
>> +               return -ENOMEM;
>> +
>> +       offset += ipa_ctx->smem_offset;
>> +
>> +       payload = ipahal_hdr_init_local_pyld(&mem, offset);
>> +       if (!payload) {
>> +               ret = -ENOMEM;
>> +               goto err_dma_free;
>> +       }
>> +
>> +       desc.type = IPA_IMM_CMD_DESC;
>> +       desc.len_opcode = IPA_IMM_CMD_HDR_INIT_LOCAL;
>> +       desc.payload = payload;
>> +
>> +       ret = ipa_send_cmd(&desc);
> 
> You have a bunch of dynamic allocations in here, which you
> then immediately tear down again after the command is complete.
> I can't see at all what you do with the DMA address, since you
> seem to not use the virtual address at all but only store
> the physical address in some kind of descriptor without ever
> writing to it.

I should probably have added at least a comment here.  The
DMA memory was zeroed at the time of allocation.  That zero
buffer is then referred to in the payload to the HDR_INIT_LOCAL
immediate command.  So that command, when executing in the IPA
hardware, uses the contents of the buffer whose physical address
it's supplied, which in this case is full of zeroes.  We don't
use the virtual address because the buffer came pre-zeroed.

Based on your comment elsewhere I will be putting the command
payload in a structure on the stack rather than allocating it
dynamically.

> Am I missing something here?
> 
>> +/* Remoteproc callbacks for SSR events: prepare, start, stop, unprepare */
>> +int ipa_ssr_prepare(struct rproc_subdev *subdev)
>> +{
>> +       printk("======== SSR prepare received ========\n");
> 
> I think you mean dev_dbg() here. A plain printk() without a level
> is not correct and we probably don't want those messages to arrive
> on the console for normal users.

Yes, this was obviously a debug message in some code I should
have removed before sending...

>> +static int ipa_firmware_load(struct de
>> +
>> +err_clear_dev:
>> +       ipa_ctx->lan_cons_ep_id = 0;
>> +       ipa_ctx->cmd_prod_ep_id = 0;
>> +       ipahal_exit();
>> +err_dma_exit:
>> +       ipa_dma_exit();
>> +err_clear_gsi:
>> +       ipa_ctx->gsi = NULL;
>> +       ipa_ctx->ipa_phys = 0;
>> +       ipa_reg_exit();
>> +err_clear_ipa_irq:
>> +       ipa_ctx->ipa_irq = 0;
>> +err_clear_filter_bitmap:
>> +       ipa_ctx->filter_bitmap = 0;
>> +err_interconnect_exit:
>> +       ipa_interconnect_exit();
>> +err_clock_exit:
>> +       ipa_clock_exit();
>> +       ipa_ctx->dev = NULL;
>> +out_smp2p_exit:
>> +       ipa_smp2p_exit(dev);
>> +
> 
> No need to initialize members to zero when you are about
> to free the structure.

The IPA context is in fact a global, static structure at
the moment.  All of this bookkeeping (zeroing out things)
is a habitual practice, basically.  Regardless your point
is good and I'll remove these kinds of things as part of
converting to not using globals.

>> +static struct platform_driver ipa_plat_drv = {
>> +       .probe = ipa_plat_drv_probe,
>> +       .remove = ipa_plat_drv_remove,
>> +       .driver = {
>> +               .name = "ipa",
>> +               .owner = THIS_MODULE,
>> +               .pm = &ipa_pm_ops,
>> +               .of_match_table = ipa_plat_drv_match,
>> +       },
>> +};
>> +
>> +builtin_platform_driver(ipa_plat_drv);
> 
> This should be module_platform_driver(), and allow unloading
> the driver.

Yes.  I've done this for my own use, but the code is not
currently able to shut down cleanly.  I've been fixing a
*lot* of the things that don't clean up after themselves
today but there's more work before I can say this can be
safely built as a module.  But it's a requirement.

					-Alex



> 
>         Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file
@ 2018-11-15  3:11       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-15  3:11 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 8:08 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> 
>> +static void ipa_client_remove_deferred(struct work_struct *work);
> 
> Try to avoid forward declarations by reordering the code in call order,
> it will also make it easier to read.
> 
>> +static DECLARE_WORK(ipa_client_remove_work, ipa_client_remove_deferred);

Done.  I've actually reworked this a lot, and pulled all the
clock (and interconnect) related code into a separate source file.
No more forward declarations (there anyway), and the work structure
is now embedded in the top-level IPA structure so I can derive
it again in the work function (rather than using the global).

>> +static struct ipa_context ipa_ctx_struct;
>> +struct ipa_context *ipa_ctx = &ipa_ctx_struct;
> 
> Global state variables should generally be removed as well, and
> passed around as function arguments.

Working on this.

>> +static int hdr_init_local_cmd(u32 offset, u32 size)
>> +{
>> +       struct ipa_desc desc = { };
>> +       struct ipa_dma_mem mem;
>> +       void *payload;
>> +       int ret;
>> +
>> +       if (ipa_dma_alloc(&mem, size, GFP_KERNEL))
>> +               return -ENOMEM;
>> +
>> +       offset += ipa_ctx->smem_offset;
>> +
>> +       payload = ipahal_hdr_init_local_pyld(&mem, offset);
>> +       if (!payload) {
>> +               ret = -ENOMEM;
>> +               goto err_dma_free;
>> +       }
>> +
>> +       desc.type = IPA_IMM_CMD_DESC;
>> +       desc.len_opcode = IPA_IMM_CMD_HDR_INIT_LOCAL;
>> +       desc.payload = payload;
>> +
>> +       ret = ipa_send_cmd(&desc);
> 
> You have a bunch of dynamic allocations in here, which you
> then immediately tear down again after the command is complete.
> I can't see at all what you do with the DMA address, since you
> seem to not use the virtual address at all but only store
> the physical address in some kind of descriptor without ever
> writing to it.

I should probably have added at least a comment here.  The
DMA memory was zeroed at the time of allocation.  That zero
buffer is then referred to in the payload to the HDR_INIT_LOCAL
immediate command.  So that command, when executing in the IPA
hardware, uses the contents of the buffer whose physical address
it's supplied, which in this case is full of zeroes.  We don't
use the virtual address because the buffer came pre-zeroed.

Based on your comment elsewhere I will be putting the command
payload in a structure on the stack rather than allocating it
dynamically.

> Am I missing something here?
> 
>> +/* Remoteproc callbacks for SSR events: prepare, start, stop, unprepare */
>> +int ipa_ssr_prepare(struct rproc_subdev *subdev)
>> +{
>> +       printk("======== SSR prepare received ========\n");
> 
> I think you mean dev_dbg() here. A plain printk() without a level
> is not correct and we probably don't want those messages to arrive
> on the console for normal users.

Yes, this was obviously a debug message in some code I should
have removed before sending...

>> +static int ipa_firmware_load(struct de
>> +
>> +err_clear_dev:
>> +       ipa_ctx->lan_cons_ep_id = 0;
>> +       ipa_ctx->cmd_prod_ep_id = 0;
>> +       ipahal_exit();
>> +err_dma_exit:
>> +       ipa_dma_exit();
>> +err_clear_gsi:
>> +       ipa_ctx->gsi = NULL;
>> +       ipa_ctx->ipa_phys = 0;
>> +       ipa_reg_exit();
>> +err_clear_ipa_irq:
>> +       ipa_ctx->ipa_irq = 0;
>> +err_clear_filter_bitmap:
>> +       ipa_ctx->filter_bitmap = 0;
>> +err_interconnect_exit:
>> +       ipa_interconnect_exit();
>> +err_clock_exit:
>> +       ipa_clock_exit();
>> +       ipa_ctx->dev = NULL;
>> +out_smp2p_exit:
>> +       ipa_smp2p_exit(dev);
>> +
> 
> No need to initialize members to zero when you are about
> to free the structure.

The IPA context is in fact a global, static structure at
the moment.  All of this bookkeeping (zeroing out things)
is a habitual practice, basically.  Regardless your point
is good and I'll remove these kinds of things as part of
converting to not using globals.

>> +static struct platform_driver ipa_plat_drv = {
>> +       .probe = ipa_plat_drv_probe,
>> +       .remove = ipa_plat_drv_remove,
>> +       .driver = {
>> +               .name = "ipa",
>> +               .owner = THIS_MODULE,
>> +               .pm = &ipa_pm_ops,
>> +               .of_match_table = ipa_plat_drv_match,
>> +       },
>> +};
>> +
>> +builtin_platform_driver(ipa_plat_drv);
> 
> This should be module_platform_driver(), and allow unloading
> the driver.

Yes.  I've done this for my own use, but the code is not
currently able to shut down cleanly.  I've been fixing a
*lot* of the things that don't clean up after themselves
today but there's more work before I can say this can be
safely built as a module.  But it's a requirement.

					-Alex



> 
>         Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 10/12] soc: qcom: ipa: data path
  2018-11-07 14:55     ` Arnd Bergmann
@ 2018-11-15  3:31       ` Alex Elder
  -1 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-15  3:31 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On 11/7/18 8:55 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> This patch contains "ipa_dp.c", which includes the bulk of the data
>> path code.  There is an overview in the code of how things operate,
>> but there are already plans to rework this portion of the driver.
>>
>> In particular:
>>   - Interrupt handling will be replaced with a threaded interrupt
>>     handler.  Currently handling occurs in a combination of
>>     interrupt and workqueue context, and this requires locking
>>     and atomic operations for proper synchronization.
> 
> You probably don't want to use just a threaded IRQ handler to
> start the poll function, that would still require an extra indirection.

That's a really good point.  However I think that the path I'll
take to *getting* to scheduling the poll in interrupt context
will use a threaded interrupt handler.  I'm hoping that will
allow me to simplify the code in steps.

The main reason for this split between working in interrupt
context when possible, but pushing to a workqueue when not, is
to allow IPA clock(s) to be turned off.  Enabling the clocks
is a blocking operation, so can't' be done in the top half
interrupt handler.  The thought was it would be best to work
in interrupt context--if the clock was already active--but
to defer to a workqueue to turn the clock on if necessary.

The result requires locking and duplication of code that I
find to be pretty confusing--and hard to reason about.  I
have been planning to re-do things to be better suited to
NAPI, and knowing that, I haven't given the data path as
much attention as some of the rest.

> However, you can probably use the top half of the threaded
> handler to request the poll function if necessary but use
> the bottom half for anything that does not go through poll.
> 
>>   - Currently, only receive endpoints use NAPI.  Transmit
>>     completion interrupts are disabled, and are handled in batches
>>     by periodically scheduling an interrupting no-op request.
>>     The plan is to arrange for transmit requests to generate
>>     interrupts, and their completion will be processed with other
>>     completions in the NAPI poll function.  This will also allow
>>     accurate feedback about packet sojourn time to be provided to
>>     queue limiting mechanisms.
> 
> Right, that is definitely required here. I also had a look at
> the gsi_channel_queue() function, which sits in the middle of
> the transmit function and is rather unoptimized. I'd suggest moving
> that into the caller so we can see what is going on, and then
> optimizing it from there.

Yes, I agree with that.  There are multiple levels of abstraction
in play and they aren't helpful.  We have ipa_desc structures that
are translated by ipa_send() into gsi_xfer_elem structures, which
are ultimately recorded by gsi_channel_queue() as 16 byte gsi_tre
structures.  At least one of those translations can go away.

>>   - Not all receive endpoints use NAPI.  The plan is for *all*
>>     endpoints to use NAPI.  And because all endpoints share a
>>     common GSI interrupt, a single NAPI structure will used to
>>     managing the processing for all completions on all endpoints.
>>   - Receive buffers are posted to the hardware by a workqueue
>>     function.  Instead, the plan is to have this done by the
>>     NAPI poll routine.
> 
> Makes sense, yes.

Thanks.

					-Alex

> 
>       Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 10/12] soc: qcom: ipa: data path
@ 2018-11-15  3:31       ` Alex Elder
  0 siblings, 0 replies; 76+ messages in thread
From: Alex Elder @ 2018-11-15  3:31 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/7/18 8:55 AM, Arnd Bergmann wrote:
> On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
>>
>> This patch contains "ipa_dp.c", which includes the bulk of the data
>> path code.  There is an overview in the code of how things operate,
>> but there are already plans to rework this portion of the driver.
>>
>> In particular:
>>   - Interrupt handling will be replaced with a threaded interrupt
>>     handler.  Currently handling occurs in a combination of
>>     interrupt and workqueue context, and this requires locking
>>     and atomic operations for proper synchronization.
> 
> You probably don't want to use just a threaded IRQ handler to
> start the poll function, that would still require an extra indirection.

That's a really good point.  However I think that the path I'll
take to *getting* to scheduling the poll in interrupt context
will use a threaded interrupt handler.  I'm hoping that will
allow me to simplify the code in steps.

The main reason for this split between working in interrupt
context when possible, but pushing to a workqueue when not, is
to allow IPA clock(s) to be turned off.  Enabling the clocks
is a blocking operation, so can't' be done in the top half
interrupt handler.  The thought was it would be best to work
in interrupt context--if the clock was already active--but
to defer to a workqueue to turn the clock on if necessary.

The result requires locking and duplication of code that I
find to be pretty confusing--and hard to reason about.  I
have been planning to re-do things to be better suited to
NAPI, and knowing that, I haven't given the data path as
much attention as some of the rest.

> However, you can probably use the top half of the threaded
> handler to request the poll function if necessary but use
> the bottom half for anything that does not go through poll.
> 
>>   - Currently, only receive endpoints use NAPI.  Transmit
>>     completion interrupts are disabled, and are handled in batches
>>     by periodically scheduling an interrupting no-op request.
>>     The plan is to arrange for transmit requests to generate
>>     interrupts, and their completion will be processed with other
>>     completions in the NAPI poll function.  This will also allow
>>     accurate feedback about packet sojourn time to be provided to
>>     queue limiting mechanisms.
> 
> Right, that is definitely required here. I also had a look at
> the gsi_channel_queue() function, which sits in the middle of
> the transmit function and is rather unoptimized. I'd suggest moving
> that into the caller so we can see what is going on, and then
> optimizing it from there.

Yes, I agree with that.  There are multiple levels of abstraction
in play and they aren't helpful.  We have ipa_desc structures that
are translated by ipa_send() into gsi_xfer_elem structures, which
are ultimately recorded by gsi_channel_queue() as 16 byte gsi_tre
structures.  At least one of those translations can go away.

>>   - Not all receive endpoints use NAPI.  The plan is for *all*
>>     endpoints to use NAPI.  And because all endpoints share a
>>     common GSI interrupt, a single NAPI structure will used to
>>     managing the processing for all completions on all endpoints.
>>   - Receive buffers are posted to the hardware by a workqueue
>>     function.  Instead, the plan is to have this done by the
>>     NAPI poll routine.
> 
> Makes sense, yes.

Thanks.

					-Alex

> 
>       Arnd
> 

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
  2018-11-15  2:48       ` Alex Elder
@ 2018-11-15 14:42         ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-15 14:42 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 14, 2018 at 6:48 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 9:00 AM, Arnd Bergmann wrote:
> > On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> >> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
> >> new file mode 100644
> >> index 000000000000..5e0aa6163235
> >> --- /dev/null
> >> +++ b/drivers/net/ipa/ipa_reg.c
> >> @@ -0,0 +1,972 @@

> It may be that this parse/construct mechanism isn't justified
> at this point.  I thought the way it presented a generic
> interface was useful, but with just one (initial) hardware
> target we don't (yet) realize its potential benefit.  It could
> be added back later, as support for new versions is added.
>
> As of now I don't plan to change this, but if you or someone
> else feels it would be better without it I can do that.

Fair enough. I do think that it would be better to avoid this, but
it's much less important than the other issues I mentioned,
so please put this on the bottom of your priorities list, we
can talk about it again when we get closer to merging the driver.

     Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction
@ 2018-11-15 14:42         ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-15 14:42 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 14, 2018 at 6:48 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 9:00 AM, Arnd Bergmann wrote:
> > On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> >> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
> >> new file mode 100644
> >> index 000000000000..5e0aa6163235
> >> --- /dev/null
> >> +++ b/drivers/net/ipa/ipa_reg.c
> >> @@ -0,0 +1,972 @@

> It may be that this parse/construct mechanism isn't justified
> at this point.  I thought the way it presented a generic
> interface was useful, but with just one (initial) hardware
> target we don't (yet) realize its potential benefit.  It could
> be added back later, as support for new versions is added.
>
> As of now I don't plan to change this, but if you or someone
> else feels it would be better without it I can do that.

Fair enough. I do think that it would be better to avoid this, but
it's much less important than the other issues I mentioned,
so please put this on the bottom of your priorities list, we
can talk about it again when we get closer to merging the driver.

     Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* Re: [RFC PATCH 10/12] soc: qcom: ipa: data path
  2018-11-15  3:31       ` Alex Elder
@ 2018-11-15 14:48         ` Arnd Bergmann
  -1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-15 14:48 UTC (permalink / raw)
  To: Alex Elder
  Cc: David Miller, Bjorn Andersson, Ilias Apalodimas, Networking,
	DTML, linux-arm-msm, linux-soc, Linux ARM,
	Linux Kernel Mailing List, syadagir, mjavid, Rob Herring,
	Mark Rutland

On Wed, Nov 14, 2018 at 7:31 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 8:55 AM, Arnd Bergmann wrote:
> > On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> >>
> >> This patch contains "ipa_dp.c", which includes the bulk of the data
> >> path code.  There is an overview in the code of how things operate,
> >> but there are already plans to rework this portion of the driver.
> >>
> >> In particular:
> >>   - Interrupt handling will be replaced with a threaded interrupt
> >>     handler.  Currently handling occurs in a combination of
> >>     interrupt and workqueue context, and this requires locking
> >>     and atomic operations for proper synchronization.
> >
> > You probably don't want to use just a threaded IRQ handler to
> > start the poll function, that would still require an extra indirection.
>
> That's a really good point.  However I think that the path I'll
> take to *getting* to scheduling the poll in interrupt context
> will use a threaded interrupt handler.  I'm hoping that will
> allow me to simplify the code in steps.
>
> The main reason for this split between working in interrupt
> context when possible, but pushing to a workqueue when not, is
> to allow IPA clock(s) to be turned off.  Enabling the clocks
> is a blocking operation, so can't' be done in the top half
> interrupt handler.  The thought was it would be best to work
> in interrupt context--if the clock was already active--but
> to defer to a workqueue to turn the clock on if necessary.
>
> The result requires locking and duplication of code that I
> find to be pretty confusing--and hard to reason about.  I
> have been planning to re-do things to be better suited to
> NAPI, and knowing that, I haven't given the data path as
> much attention as some of the rest.

Right, that sounds like a good plan: start making it use a
threaded IRQ handler first to clean up the code, and then
think about optimizing the NAPI wakeup once that works
reliably.

I think what you can do here eventually is to have
a combined threaded/non-threaded IRQ handler, where
the threaded handler can do everything it needs to do,
and the non-threaded handler does only one thing:
if all conditions are met for entering the NAPI handler
(waiting for rx/tx IRQ, clocks enabled, ...) we call
napi_schedule(), otherwise defer to the threaded handler.

       Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

* [RFC PATCH 10/12] soc: qcom: ipa: data path
@ 2018-11-15 14:48         ` Arnd Bergmann
  0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2018-11-15 14:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 14, 2018 at 7:31 PM Alex Elder <elder@linaro.org> wrote:
>
> On 11/7/18 8:55 AM, Arnd Bergmann wrote:
> > On Wed, Nov 7, 2018 at 1:33 AM Alex Elder <elder@linaro.org> wrote:
> >>
> >> This patch contains "ipa_dp.c", which includes the bulk of the data
> >> path code.  There is an overview in the code of how things operate,
> >> but there are already plans to rework this portion of the driver.
> >>
> >> In particular:
> >>   - Interrupt handling will be replaced with a threaded interrupt
> >>     handler.  Currently handling occurs in a combination of
> >>     interrupt and workqueue context, and this requires locking
> >>     and atomic operations for proper synchronization.
> >
> > You probably don't want to use just a threaded IRQ handler to
> > start the poll function, that would still require an extra indirection.
>
> That's a really good point.  However I think that the path I'll
> take to *getting* to scheduling the poll in interrupt context
> will use a threaded interrupt handler.  I'm hoping that will
> allow me to simplify the code in steps.
>
> The main reason for this split between working in interrupt
> context when possible, but pushing to a workqueue when not, is
> to allow IPA clock(s) to be turned off.  Enabling the clocks
> is a blocking operation, so can't' be done in the top half
> interrupt handler.  The thought was it would be best to work
> in interrupt context--if the clock was already active--but
> to defer to a workqueue to turn the clock on if necessary.
>
> The result requires locking and duplication of code that I
> find to be pretty confusing--and hard to reason about.  I
> have been planning to re-do things to be better suited to
> NAPI, and knowing that, I haven't given the data path as
> much attention as some of the rest.

Right, that sounds like a good plan: start making it use a
threaded IRQ handler first to clean up the code, and then
think about optimizing the NAPI wakeup once that works
reliably.

I think what you can do here eventually is to have
a combined threaded/non-threaded IRQ handler, where
the threaded handler can do everything it needs to do,
and the non-threaded handler does only one thing:
if all conditions are met for entering the NAPI handler
(waiting for rx/tx IRQ, clocks enabled, ...) we call
napi_schedule(), otherwise defer to the threaded handler.

       Arnd

^ permalink raw reply	[flat|nested] 76+ messages in thread

end of thread, other threads:[~2018-11-15 14:48 UTC | newest]

Thread overview: 76+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-11-07  0:32 [RFC PATCH 00/12] net: introduce Qualcomm IPA driver Alex Elder
2018-11-07  0:32 ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 01/12] dt-bindings: soc: qcom: add IPA bindings Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 11:50   ` Arnd Bergmann
2018-11-07 11:50     ` Arnd Bergmann
2018-11-09 22:38     ` Alex Elder
2018-11-09 22:38       ` Alex Elder
2018-11-07 14:59   ` Rob Herring
2018-11-07 14:59     ` Rob Herring
2018-11-09 22:38     ` Alex Elder
2018-11-09 22:38       ` Alex Elder
2018-11-11  1:40       ` Rob Herring
2018-11-11  1:40         ` Rob Herring
2018-11-11  1:40         ` Rob Herring
2018-11-13 16:28     ` Alex Elder
2018-11-13 16:28       ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 02/12] soc: qcom: ipa: DMA helpers Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 12:17   ` Arnd Bergmann
2018-11-07 12:17     ` Arnd Bergmann
2018-11-13 16:33     ` Alex Elder
2018-11-13 16:33       ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 03/12] soc: qcom: ipa: generic software interface Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 04/12] soc: qcom: ipa: immediate commands Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 14:36   ` Arnd Bergmann
2018-11-07 14:36     ` Arnd Bergmann
2018-11-13 16:58     ` Alex Elder
2018-11-13 16:58       ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 05/12] soc: qcom: ipa: IPA interrupts and the microcontroller Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 06/12] soc: qcom: ipa: QMI modem communication Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 07/12] soc: qcom: ipa: IPA register abstraction Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 15:00   ` Arnd Bergmann
2018-11-07 15:00     ` Arnd Bergmann
2018-11-15  2:48     ` Alex Elder
2018-11-15  2:48       ` Alex Elder
2018-11-15 14:42       ` Arnd Bergmann
2018-11-15 14:42         ` Arnd Bergmann
2018-11-07  0:32 ` [RFC PATCH 08/12] soc: qcom: ipa: utility functions Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 09/12] soc: qcom: ipa: main IPA source file Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 14:08   ` Arnd Bergmann
2018-11-07 14:08     ` Arnd Bergmann
2018-11-15  3:11     ` Alex Elder
2018-11-15  3:11       ` Alex Elder
2018-11-07  0:32 ` [RFC PATCH 10/12] soc: qcom: ipa: data path Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 14:55   ` Arnd Bergmann
2018-11-07 14:55     ` Arnd Bergmann
2018-11-15  3:31     ` Alex Elder
2018-11-15  3:31       ` Alex Elder
2018-11-15 14:48       ` Arnd Bergmann
2018-11-15 14:48         ` Arnd Bergmann
2018-11-07  0:32 ` [RFC PATCH 11/12] soc: qcom: ipa: IPA rmnet interface Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07 13:30   ` Arnd Bergmann
2018-11-07 13:30     ` Arnd Bergmann
2018-11-07 15:26   ` Dan Williams
2018-11-07 15:26     ` Dan Williams
2018-11-07  0:32 ` [RFC PATCH 12/12] soc: qcom: ipa: build and "ipa_i.h" Alex Elder
2018-11-07  0:32   ` Alex Elder
2018-11-07  0:40   ` Randy Dunlap
2018-11-07  0:40     ` Randy Dunlap
2018-11-07  0:40     ` Randy Dunlap
2018-11-08 16:22     ` Alex Elder
2018-11-08 16:22       ` Alex Elder
2018-11-07 12:34   ` Arnd Bergmann
2018-11-07 12:34     ` Arnd Bergmann
2018-11-07 15:46 ` [RFC PATCH 00/12] net: introduce Qualcomm IPA driver Arnd Bergmann
2018-11-07 15:46   ` Arnd Bergmann

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.