From patchwork Fri May 31 03:53:33 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Elder X-Patchwork-Id: 165487 Delivered-To: patch@linaro.org Received: by 2002:a92:9e1a:0:0:0:0:0 with SMTP id q26csp185776ili; Thu, 30 May 2019 20:54:01 -0700 (PDT) X-Google-Smtp-Source: APXvYqx6jAXKNt64GsaspaD62LVRX1LCkyYgKwG0H7EDJyTAXPnbdXgzMUCbPsPO2IETpq2eVp4R X-Received: by 2002:a65:64d5:: with SMTP id t21mr6971727pgv.310.1559274841092; Thu, 30 May 2019 20:54:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1559274841; cv=none; d=google.com; s=arc-20160816; b=Mdyew7N9dIvUvkb+5QKch3lX6EJoYYSOwBk55itmFM3QK8oFw8lUjjKqrfpNOCfVn+ 8hkZ7dX1lPi2m8LQ6rLpNhnIUt3Jwq2NT7Uo6FjD+93hGCGElmBLMgLlIj3b9YBGTQC8 RvNtj/SolmtxCPk9uKL5AIinXji80idiz/kLkN+QyNLdzE3OGEBuN/tiFHj0nfUP2bYd qM15VleoVypJej/PROVp15hAALfx9xMrxPtAZ/ruHIwxxG36ydJDZGV8cGQ4IodILmIP QEATeVr+/OqKsvvD8csdjpFXrBBcOckPjbHvsS+dFSrMsiIu3i8iBOb5+AsDLgXQondF WBaA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=JZ95IE69sVcEBRYG+tgP+o1mXucxgv2wjwcFuiPisT8=; b=xC6ualmo+jx6cE8SeBgq4vq0TTsRdiUB3K6bIwYWYV3OikPU8GpHBJbERh8JFLO85Z z/lo8yfFrEw4dwsZaqiaHqo8pmuWrM2Lykn1ADZtBuPazc1m55nsy4ghr2m/09wgntjZ 6nlGpc+vudKCJhfWbFqw+HpY3XfSUz1hFcLiiLAcOX1Ctw72awS5gYN3yr1Vd0ltclKh AGOZKtQxFMEIDWGpBJGAUa/s8JYiLnSGO1G7QcLROZAh++Z+BXGhkdlHYIFevDEOmThP J1Wb6IYA0g7Iusa29srKNxoThciH7oIpodEiT81yKgxMWJa82k+OqFgb484817VwOvK+ W/Lg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=um96SFt2; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id d19si4601423pls.221.2019.05.30.20.54.00; Thu, 30 May 2019 20:54:01 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=um96SFt2; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726701AbfEaDx7 (ORCPT + 7 others); Thu, 30 May 2019 23:53:59 -0400 Received: from mail-it1-f196.google.com ([209.85.166.196]:51200 "EHLO mail-it1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726721AbfEaDx7 (ORCPT ); Thu, 30 May 2019 23:53:59 -0400 Received: by mail-it1-f196.google.com with SMTP id m3so13581632itl.1 for ; Thu, 30 May 2019 20:53:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=JZ95IE69sVcEBRYG+tgP+o1mXucxgv2wjwcFuiPisT8=; b=um96SFt2ycKzFq9uGSVCZXnnl+5VpTPczH63AkH78WbURU0HUg0oQkJqruuHV0ekPf AA8QjULtHn5OjoK6WV2NoNuhF6m9yfc8wRoJZcor4/rN+zgLvhXNPNNNaD+506eWeiks b65grxnRRhReyKn2e9jgp2KTK+5pfby9Eo137NqKOnRPuk2aMIudpwXCI3+D3h3v/g8S BdBznrfedr5Dhtdi/DwhnleSg1EHCzi3Ol6NsCk1py2XRGGeDpr+9cGA5MCc1wWPhVmU CznBta7yL7k7P1+WkhCQIjJChcuLD3mOcnO+9aKy45Z2WPNqdnEczTSZwVWbqXLhTxXC 3aiA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=JZ95IE69sVcEBRYG+tgP+o1mXucxgv2wjwcFuiPisT8=; b=BTdq0MVE71XRnLMzOi96SKbony3TszZgblF4BlL/nh7Tf8kUFDs8hHpuN3qbTjIOgn x8Jf7B69ZL/OXj5MyjO4QJpek6Lk3F0n8jNd2SCAoUPSDqPyC+uSu1uU7EPWga5jMIiT hlGd13UQ5Abq/dVsxHIMItNQtSNHmTfYBNaYcCXEYAn4jpjDV6+mOFhEdHiRV4pK2VOv kU0IHgCy3HI5erPN6xfyUDJT2pDE39tJyWDc2uHpp1woRSaaGwTzVCHQRujMt3wfLuY9 a9/W6vnGUSQ6O700zop9xRlAI9HeH8T8k1t0pv625kiKOtrf/H8JsvIyJiJdOot5XpdK mNYQ== X-Gm-Message-State: APjAAAWpXo2Dl9NFIiGCtJasPDXgZYUOwcAFaoUBC9xJw0XziwXwiNSG YPamV4n6jX8HG9q3BwBI6CaVNg== X-Received: by 2002:a05:660c:712:: with SMTP id l18mr5737921itk.169.1559274838050; Thu, 30 May 2019 20:53:58 -0700 (PDT) Received: from localhost.localdomain (c-71-195-29-92.hsd1.mn.comcast.net. [71.195.29.92]) by smtp.gmail.com with ESMTPSA id q15sm1626947ioi.15.2019.05.30.20.53.56 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 30 May 2019 20:53:57 -0700 (PDT) From: Alex Elder To: davem@davemloft.net, arnd@arndb.de, bjorn.andersson@linaro.org, ilias.apalodimas@linaro.org, robh+dt@kernel.org, mark.rutland@arm.com, devicetree@vger.kernel.org Cc: evgreen@chromium.org, benchan@google.com, ejcaruso@google.com, cpratapa@codeaurora.org, syadagir@codeaurora.org, subashab@codeaurora.org, abhishek.esse@gmail.com, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-soc@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org Subject: [PATCH v2 02/17] dt-bindings: soc: qcom: add IPA bindings Date: Thu, 30 May 2019 22:53:33 -0500 Message-Id: <20190531035348.7194-3-elder@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190531035348.7194-1-elder@linaro.org> References: <20190531035348.7194-1-elder@linaro.org> MIME-Version: 1.0 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add the binding definitions for the "qcom,ipa" device tree node. Signed-off-by: Alex Elder --- .../devicetree/bindings/net/qcom,ipa.yaml | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/qcom,ipa.yaml -- 2.20.1 diff --git a/Documentation/devicetree/bindings/net/qcom,ipa.yaml b/Documentation/devicetree/bindings/net/qcom,ipa.yaml new file mode 100644 index 000000000000..0037fc278a61 --- /dev/null +++ b/Documentation/devicetree/bindings/net/qcom,ipa.yaml @@ -0,0 +1,180 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/qcom,ipa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm IP Accelerator (IPA) + +maintainers: + - Alex Elder + +description: + 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 sits between multiple independent "execution environments," + including the Application Processor (AP) 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. + + See also soc/qcom/qcom,smp2p.txt and interconnect/interconnect.txt. + + - | + -------- --------- + | | | | + | AP +<---. .----+ Modem | + | +--. | | .->+ | + | | | | | | | | + -------- | | | | --------- + v | v | + --+-+---+-+-- + | GSI | + |-----------| + | | + | IPA | + | | + ------------- + +properties: + compatible: + const: "qcom,sdm845-ipa" + + reg: + items: + - description: IPA registers + - description: IPA shared memory + - description: GSI registers + + reg-names: + items: + - const: ipa-reg + - const: ipa-shared + - const: gsi + + clocks: + maxItems: 1 + + clock-names: + const: core + + interrupts: + items: + - description: IPA interrupt (hardware IRQ) + - description: GSI interrupt (hardware IRQ) + - description: Modem clock query interrupt (smp2p interrupt) + - description: Modem setup ready interrupt (smp2p interrupt) + + interrupt-names: + items: + - const: ipa + - const: gsi + - const: ipa-clock-query + - const: ipa-setup-ready + + interconnects: + items: + - description: Interconnect path between IPA and main memory + - description: Interconnect path between IPA and internal memory + - description: Interconnect path between IPA and the AP subsystem + + interconnect-names: + items: + - const: memory + - const: imem + - const: config + + qcom,smem-states: + description: State bits used in by the AP to signal the modem. + items: + - description: Whether the "ipa-clock-enabled" state bit is valid + - description: Whether the IPA clock is enabled (if valid) + + qcom,smem-state-names: + description: The names of the state bits used for SMP2P output + items: + - const: ipa-clock-enabled-valid + - const: ipa-clock-enabled + + modem-init: + type: boolean + description: + If present, it indicates that the modem is responsible for + performing early IPA initialization, including loading and + validating firwmare used by the GSI. + + memory-region: + maxItems: 1 + description: + If present, a phandle for a reserved memory area that holds + the firmware passed to Trust Zone for authentication. Required + when Trust Zone (not the modem) performs early initialization. + +required: + - compatible + - reg + - clocks + - interrupts + - interconnects + - qcom,smem-states + +oneOf: + - required: + - modem-init + - required: + - memory-region + +examples: + - | + smp2p-mpss { + 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@1e40000 { + compatible = "qcom,sdm845-ipa"; + + modem-init; + + reg = <0 0x1e40000 0 0x7000>, + <0 0x1e47000 0 0x2000>, + <0 0x1e04000 0 0x2c000>; + reg-names = "ipa-reg", + "ipa-shared"; + "gsi"; + + interrupts-extended = <&intc 0 311 IRQ_TYPE_EDGE_RISING>, + <&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-setup-ready"; + + clocks = <&rpmhcc RPMH_IPA_CLK>; + clock-names = "core"; + + interconnects = + <&rsc_hlos MASTER_IPA &rsc_hlos SLAVE_EBI1>, + <&rsc_hlos MASTER_IPA &rsc_hlos SLAVE_IMEM>, + <&rsc_hlos MASTER_APPSS_PROC &rsc_hlos 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"; + }; From patchwork Fri May 31 03:53:38 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Elder X-Patchwork-Id: 165492 Delivered-To: patch@linaro.org Received: by 2002:a92:9e1a:0:0:0:0:0 with SMTP id q26csp185919ili; Thu, 30 May 2019 20:54:09 -0700 (PDT) X-Google-Smtp-Source: APXvYqxz2HBuknXXEKN1zc+yW65QKiPC0fNkPP1z1mpvPPhKO9o5O1x8Nq2YZE5BW5SxJTEKOoMu X-Received: by 2002:a17:90a:33c5:: with SMTP id n63mr6829799pjb.16.1559274849524; Thu, 30 May 2019 20:54:09 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1559274849; cv=none; d=google.com; s=arc-20160816; b=iFttAQv+2hggLIfccCHp4TKRitojSFXYqs+oJe+9eiycvu7nJmVVlzfUUHFnO6lc2W vLtpR/O7hUEKIKN8H1/mB9WkzuhR4rIDhyWK8SoPWxtcx5Mq/v733uwz0h/xIIpZydgq 7YlQuCoVrv4ok8xy0gNcbB+URWeqUrDwvsEZnZaquBtPN6weaOaMcoE0CucttWTTsUqz P0yT3GToCk+8NdVak3tpG/p/aKaarGOy/ATEUpD1FZ9KmLUokwrDqXLgoJIKHez0Qkzr zrHCw3ZGf5V82r8n4zqYj2yxwe7KrrSyZjSy/sgtsS/IIasXHHdoUdKlCGUQC0wh/68P eo0A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=zl6Ym3k52iiA7503Ws3+JCBahS2qMDspj8g010dFM+E=; b=S9kTBRULtRTWby4b0oLPjx54n/33bWEBiNeHQPqX1GlPXsNa6NiMFuwwffYBdW3xZY RwB+nPlZnyeBltkSNW1+HrPXWTTQ+nOy/k7opjnU+dzor9aFpTK4UC6nOhCBX7Q7wUWs UTMb9cdZrmFBP9b9WWiGhdCPQTI/R79iLNbEMv6okPyUwbO9eTdYljFIwj0RnHh9XF9k Meh/XUDPmcstY8lTzBW/cSIPmcWRzisCq8BUIwD8I3hcQqB3lS6MxiFOqKJmKajrfyqV TSWX0BVocsa8/tX8QYjND5AheCV3V4rYy5SVO7mBL14BEv7tBr6mpzwWNb3/2ZtZ3dwJ 68Pw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=DEA2ltuF; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id d3si4792120pgg.378.2019.05.30.20.54.09; Thu, 30 May 2019 20:54:09 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=DEA2ltuF; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726876AbfEaDyI (ORCPT + 7 others); Thu, 30 May 2019 23:54:08 -0400 Received: from mail-it1-f194.google.com ([209.85.166.194]:40089 "EHLO mail-it1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726860AbfEaDyH (ORCPT ); Thu, 30 May 2019 23:54:07 -0400 Received: by mail-it1-f194.google.com with SMTP id h11so13031664itf.5 for ; Thu, 30 May 2019 20:54:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=zl6Ym3k52iiA7503Ws3+JCBahS2qMDspj8g010dFM+E=; b=DEA2ltuFdf0EhwWyJVJvzh3qxKbVsjPSzgL5dE/zkzT8ROS6gtbB71UPuEORu+B6RR 3/+Ubn8XucNa4a4D19UqkRcnDoTDNtB6aB18XT4O40r+jRn6LMPiWa7Bgm47ad/Goj+q 8JLn5hZk82p1rhl8dqGUtMItMa5Rkg7H7HIAZJAmU69phjoCKPMrTt7SODkC8+CKvWFR 1DMRvP+lDww/ViN2h+LWPNFZDOlVgc1c8bt6OipoXiB5wVnPu2D7R6Tddij9M3NvdZNG cmNq8tcTk2ZcWfO3fKa1D7azkROYwj6L5U1s4t287nOiP0M3YIQBu0zVztrSm7jQPQ6R vhuA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=zl6Ym3k52iiA7503Ws3+JCBahS2qMDspj8g010dFM+E=; b=UPz+dfBDyxcebJiPuOw6NELNeju7wAVPOQn0VgFhfKf7rtOCyV9tv9n0PfITPMylsE FR4XrUC05GvgUTOo266Jxde0ee/mWoRwhxbeqArpPq4LpwxDkBvnkNrwDLgldegQSSfS 1V2LYjjQgpMBgTtgkQtzZEBoYOLxAz+aJZN1JtjDjvWOh/2ZZRLcwcjNYJrxSUR4pX1s jEk2qQf+Mo92zsw0JqzPqd6vNXl0mbA3giXX8JqVyrQW/Aly2sbs+JYSWQDXYC4a3g0i unMCA88ERpvCkdCJkFmQl3Mon6gwGlfCaNO+MDo8SlMhDi+AC7LTjGYLemNNbTX/dI9t 9yNA== X-Gm-Message-State: APjAAAWfQTspxoeVcQ002rIOUC2ATlba7DpJFRH04N+mtraumd7O3/ji QTEeDvz5Yc47RebMGyHHOdFfjQ== X-Received: by 2002:a24:47cc:: with SMTP id t195mr5276615itb.117.1559274845341; Thu, 30 May 2019 20:54:05 -0700 (PDT) Received: from localhost.localdomain (c-71-195-29-92.hsd1.mn.comcast.net. [71.195.29.92]) by smtp.gmail.com with ESMTPSA id q15sm1626947ioi.15.2019.05.30.20.54.03 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 30 May 2019 20:54:04 -0700 (PDT) From: Alex Elder To: davem@davemloft.net, arnd@arndb.de, bjorn.andersson@linaro.org, ilias.apalodimas@linaro.org Cc: evgreen@chromium.org, benchan@google.com, ejcaruso@google.com, cpratapa@codeaurora.org, syadagir@codeaurora.org, subashab@codeaurora.org, abhishek.esse@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-soc@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org Subject: [PATCH v2 07/17] soc: qcom: ipa: the generic software interface Date: Thu, 30 May 2019 22:53:38 -0500 Message-Id: <20190531035348.7194-8-elder@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190531035348.7194-1-elder@linaro.org> References: <20190531035348.7194-1-elder@linaro.org> MIME-Version: 1.0 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org This patch includes "gsi.c", which implements the generic software interface (GSI) for IPA. The generic software interface abstracts channels, which provide a means of transferring data either from the AP to the IPA, or from the IPA to the AP. A ring buffer of "transfer elements" (TREs) is used to describe data transfers to perform. The AP writes a doorbell register associated with a channel to let it know it has added new entries (for an AP->IPA channel) or has finished processing entries (for an IPA->AP channel). Each channel also has an event ring buffer, used by the IPA to communicate information about events related to a channel (for example, the completion of TREs). The IPA writes its own doorbell register, which triggers an interrupt on the AP, to signal that new event information has arrived. Signed-off-by: Alex Elder --- drivers/net/ipa/gsi.c | 1635 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1635 insertions(+) create mode 100644 drivers/net/ipa/gsi.c -- 2.20.1 diff --git a/drivers/net/ipa/gsi.c b/drivers/net/ipa/gsi.c new file mode 100644 index 000000000000..a749d3b0d792 --- /dev/null +++ b/drivers/net/ipa/gsi.c @@ -0,0 +1,1635 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018-2019 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gsi.h" +#include "gsi_reg.h" +#include "gsi_private.h" +#include "gsi_trans.h" +#include "ipa_gsi.h" +#include "ipa_data.h" + +/** + * DOC: The IPA Generic Software Interface + * + * The generic software interface (GSI) is an integral component of the IPA, + * providing a well-defined communication layer between the AP subsystem + * and the IPA core. The modem uses the GSI layer as well. + * + * -------- --------- + * | | | | + * | AP +<---. .----+ Modem | + * | +--. | | .->+ | + * | | | | | | | | + * -------- | | | | --------- + * v | v | + * --+-+---+-+-- + * | GSI | + * |-----------| + * | | + * | IPA | + * | | + * ------------- + * + * In the above diagram, the AP 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 TRE 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 within the GSI. + * + * 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. + * The GSI then writes its doorbell for the event ring, causing the target + * EE to be interrupted. Each entry in an event ring contains a pointer + * to the channel TRE whose completion the event represents. + * + * Each TRE in a channel ring has a set of flags. One flag indicates whether + * the completion of the transfer operation generates an entry (and possibly + * an interrupt) in the channel's event ring. Other flags allow transfer + * elements to be chained together, forming a single logical transaction. + * TRE flags are used to control whether and when interrupts are generated + * to signal completion of channel transfers. + * + * 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 communicate the + * completion of many transfers. + * + * Note that all GSI registers are little-endian, which is the assumed + * endianness of I/O space accesses. The accessor functions perform byte + * swapping if needed (i.e., for a big endian CPU). + */ + +/* Delay period for interrupt moderation (in 32KHz IPA internal timer ticks) */ +#define IPA_GSI_EVT_RING_INT_MODT (32 * 1) /* 1ms under 32KHz clock */ + +#define GSI_CMD_TIMEOUT 5 /* seconds */ + +#define GSI_MHI_ER_START 10 /* First reserved event number */ +#define GSI_MHI_ER_END 16 /* Last reserved event number */ + +#define GSI_ISR_MAX_ITER 50 /* Detect interrupt storms */ + +/* Hardware values from the error log register error 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 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, +}; + +/* Fields in an error log register at GSI_ERROR_LOG_OFFSET */ +#define GSI_LOG_ERR_ARG3_FMASK GENMASK(3, 0) +#define GSI_LOG_ERR_ARG2_FMASK GENMASK(7, 4) +#define GSI_LOG_ERR_ARG1_FMASK GENMASK(11, 8) +#define GSI_LOG_ERR_CODE_FMASK GENMASK(15, 12) +#define GSI_LOG_ERR_VIRT_IDX_FMASK GENMASK(23, 19) +#define GSI_LOG_ERR_TYPE_FMASK GENMASK(27, 24) +#define GSI_LOG_ERR_EE_FMASK GENMASK(31, 28) + +/* Hardware values used when programming an event ring */ +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 */ +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 representing 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, +}; + +/* Hardware values representing 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, +}; + +/** gsi_gpi_channel_scratch - GPI protocol scratch register + * + * @max_outstanding_tre: + * Defines the maximum number of TREs allowed in a single transaction + * on a channel (in Bytes). This determines the amount of prefetch + * performed by the hardware. We configure this to equal the size of + * the TLV FIFO for the channel. + * @outstanding_threshold: + * Defines the threshold (in Bytes) determining when the sequencer + * should update the channel doorbell. We configure this to equal + * the size of two TREs. + */ +struct gsi_gpi_channel_scratch { + u64 reserved1; + u16 reserved2; + u16 max_outstanding_tre; + u16 reserved3; + u16 outstanding_threshold; +}; + +/** gsi_channel_scratch - channel scratch configuration area + * + * The exact interpretation of this register is protocol-specific. + * We only use GPI channels; see struct gsi_gpi_channel_scratch, above. + */ +union gsi_channel_scratch { + struct gsi_gpi_channel_scratch gpi; + struct { + u32 word1; + u32 word2; + u32 word3; + u32 word4; + } data; +}; + +/* Return the channel id associated with a given channel */ +static u32 gsi_channel_id(struct gsi_channel *channel) +{ + return channel - &channel->gsi->channel[0]; +} + +/* Report the number of bytes queued to hardware since last call */ +void gsi_channel_tx_queued(struct gsi_channel *channel) +{ + u32 trans_count; + u32 byte_count; + + trans_count = channel->trans_count - channel->queued_trans_count; + byte_count = channel->byte_count - channel->queued_byte_count; + channel->queued_trans_count = channel->trans_count; + channel->queued_byte_count = channel->byte_count; + + ipa_gsi_channel_tx_queued(channel->gsi, gsi_channel_id(channel), + trans_count, byte_count); +} + +static void gsi_irq_event_enable(struct gsi *gsi, u32 evt_ring_id) +{ + u32 val; + + gsi->event_enable_bitmap |= BIT(evt_ring_id); + val = gsi->event_enable_bitmap; + iowrite32(val, gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFSET); +} + +static void gsi_irq_event_disable(struct gsi *gsi, u32 evt_ring_id) +{ + u32 val; + + gsi->event_enable_bitmap &= ~BIT(evt_ring_id); + val = gsi->event_enable_bitmap; + iowrite32(val, gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFSET); +} + +/* Enable all GSI_interrupt types */ +static void gsi_irq_enable(struct gsi *gsi) +{ + u32 val; + + /* Inter EE commands / interrupt are not supported. */ + val = GSI_CNTXT_TYPE_IRQ_MSK_ALL; + iowrite32(val, gsi->virt + GSI_CNTXT_TYPE_IRQ_MSK_OFFSET); + + val = GENMASK(GSI_CHANNEL_MAX - 1, 0); + iowrite32(val, gsi->virt + GSI_CNTXT_SRC_CH_IRQ_MSK_OFFSET); + + val = GENMASK(GSI_EVT_RING_MAX - 1, 0); + iowrite32(val, gsi->virt + GSI_CNTXT_SRC_EV_CH_IRQ_MSK_OFFSET); + + /* Each IEOB interrupt is enabled (later) as needed by channels */ + iowrite32(0, gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFSET); + + val = GSI_CNTXT_GLOB_IRQ_ALL; + iowrite32(val, gsi->virt + GSI_CNTXT_GLOB_IRQ_EN_OFFSET); + + /* Never enable GSI_BREAK_POINT */ + val = GSI_CNTXT_GSI_IRQ_ALL & ~EN_BREAK_POINT_FMASK; + iowrite32(val, gsi->virt + GSI_CNTXT_GSI_IRQ_EN_OFFSET); +} + +/* Disable all GSI_interrupt types */ +static void gsi_irq_disable(struct gsi *gsi) +{ + iowrite32(0, gsi->virt + GSI_CNTXT_GSI_IRQ_EN_OFFSET); + iowrite32(0, gsi->virt + GSI_CNTXT_GLOB_IRQ_EN_OFFSET); + iowrite32(0, gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_MSK_OFFSET); + iowrite32(0, gsi->virt + GSI_CNTXT_SRC_EV_CH_IRQ_MSK_OFFSET); + iowrite32(0, gsi->virt + GSI_CNTXT_SRC_CH_IRQ_MSK_OFFSET); + iowrite32(0, gsi->virt + GSI_CNTXT_TYPE_IRQ_MSK_OFFSET); +} + +/* Return the hardware's notion of the current state of a channel */ +static enum gsi_channel_state gsi_channel_state(struct gsi_channel *channel) +{ + u32 channel_id = gsi_channel_id(channel); + struct gsi *gsi = channel->gsi; + u32 val; + + val = ioread32(gsi->virt + GSI_CH_C_CNTXT_0_OFFSET(channel_id)); + + return u32_get_bits(val, CHSTATE_FMASK); +} + +/* Return the hardware's notion of the current state of an event ring */ +static enum gsi_evt_ring_state +gsi_evt_ring_state(struct gsi *gsi, u32 evt_ring_id) +{ + u32 val = ioread32(gsi->virt + GSI_EV_CH_E_CNTXT_0_OFFSET(evt_ring_id)); + + return u32_get_bits(val, EV_CHSTATE_FMASK); +} + +/* Channel control interrupt handler */ +static void gsi_isr_chan_ctrl(struct gsi *gsi) +{ + u32 channel_mask; + + channel_mask = ioread32(gsi->virt + GSI_CNTXT_SRC_CH_IRQ_OFFSET); + iowrite32(channel_mask, gsi->virt + GSI_CNTXT_SRC_CH_IRQ_CLR_OFFSET); + + while (channel_mask) { + u32 channel_id = __ffs(channel_mask); + struct gsi_channel *channel; + + channel_mask ^= BIT(channel_id); + + channel = &gsi->channel[channel_id]; + channel->state = gsi_channel_state(channel); + + complete(&channel->completion); + } +} + +static void gsi_isr_evt_ctrl(struct gsi *gsi) +{ + u32 event_mask; + + event_mask = ioread32(gsi->virt + GSI_CNTXT_SRC_EV_CH_IRQ_OFFSET); + iowrite32(event_mask, gsi->virt + GSI_CNTXT_SRC_EV_CH_IRQ_CLR_OFFSET); + + while (event_mask) { + u32 evt_ring_id = __ffs(event_mask); + struct gsi_evt_ring *evt_ring; + + event_mask ^= BIT(evt_ring_id); + + evt_ring = &gsi->evt_ring[evt_ring_id]; + evt_ring->state = gsi_evt_ring_state(gsi, evt_ring_id); + + complete(&evt_ring->completion); + } +} + +static void +gsi_isr_glob_chan_err(struct gsi *gsi, u32 err_ee, u32 channel_id, u32 code) +{ + if (code == GSI_OUT_OF_RESOURCES_ERR) { + dev_err(gsi->dev, "channel %u out of resources\n", channel_id); + complete(&gsi->channel[channel_id].completion); + return; + } + + /* Report, but otherwise ignore all other error codes */ + dev_err(gsi->dev, "channel %u global error ee 0x%08x code 0x%08x\n", + channel_id, err_ee, code); +} + +static void +gsi_isr_glob_evt_err(struct gsi *gsi, u32 err_ee, u32 evt_ring_id, u32 code) +{ + if (code == GSI_OUT_OF_RESOURCES_ERR) { + struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id]; + u32 channel_id = gsi_channel_id(evt_ring->channel); + + complete(&evt_ring->completion); + dev_err(gsi->dev, "evt_ring for channel %u out of resources\n", + channel_id); + return; + } + + /* Report, but otherwise ignore all other error codes */ + dev_err(gsi->dev, "event ring %u global error ee %u code 0x%08x\n", + evt_ring_id, err_ee, code); +} + +static void gsi_isr_glob_err(struct gsi *gsi) +{ + enum gsi_err_type type; + enum gsi_err_code code; + u32 which; + u32 val; + u32 ee; + + /* Get the logged error, then reinitialize the log */ + val = ioread32(gsi->virt + GSI_ERROR_LOG_OFFSET); + iowrite32(0, gsi->virt + GSI_ERROR_LOG_OFFSET); + iowrite32(~0, gsi->virt + GSI_ERROR_LOG_CLR_OFFSET); + + ee = u32_get_bits(val, GSI_LOG_ERR_EE_FMASK); + which = u32_get_bits(val, GSI_LOG_ERR_VIRT_IDX_FMASK); + type = u32_get_bits(val, GSI_LOG_ERR_TYPE_FMASK); + code = u32_get_bits(val, GSI_LOG_ERR_CODE_FMASK); + + if (type == GSI_ERR_TYPE_CHAN) + gsi_isr_glob_chan_err(gsi, ee, which, code); + else if (type == GSI_ERR_TYPE_EVT) + gsi_isr_glob_evt_err(gsi, ee, which, code); + else /* type GSI_ERR_TYPE_GLOB should be fatal */ + dev_err(gsi->dev, "unexpected global error 0x%08x\n", type); +} + +static void gsi_isr_glob_ee(struct gsi *gsi) +{ + u32 val; + + val = ioread32(gsi->virt + GSI_CNTXT_GLOB_IRQ_STTS_OFFSET); + + if (val & ERROR_INT_FMASK) + gsi_isr_glob_err(gsi); + + iowrite32(val, gsi->virt + GSI_CNTXT_GLOB_IRQ_CLR_OFFSET); + + val &= ~ERROR_INT_FMASK; + + if (val & EN_GP_INT1_FMASK) { + dev_err(gsi->dev, "unexpected global INT1\n"); + val ^= EN_GP_INT1_FMASK; + } + + if (val) + dev_err(gsi->dev, "unexpected global interrupt 0x%08x\n", val); +} + +/* I/O completion interrupt event */ +static void gsi_isr_ieob(struct gsi *gsi) +{ + u32 event_mask; + + event_mask = ioread32(gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_OFFSET); + iowrite32(event_mask, gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFSET); + + while (event_mask) { + u32 evt_ring_id = __ffs(event_mask); + + event_mask ^= BIT(evt_ring_id); + + gsi_irq_event_disable(gsi, evt_ring_id); + napi_schedule(&gsi->evt_ring[evt_ring_id].channel->napi); + } +} + +/* We don't currently expect to receive any inter-EE channel interrupts */ +static void gsi_isr_inter_ee_chan_ctrl(struct gsi *gsi) +{ + u32 channel_mask; + + channel_mask = ioread32(gsi->virt + GSI_INTER_EE_SRC_CH_IRQ_OFFSET); + iowrite32(channel_mask, gsi->virt + GSI_INTER_EE_SRC_CH_IRQ_CLR_OFFSET); + + while (channel_mask) { + u32 channel_id = __ffs(channel_mask); + + dev_err(gsi->dev, "ch %u inter-EE interrupt\n", channel_id); + channel_mask ^= BIT(channel_id); + } +} + +/* We don't currently expect to receive any inter-EE event interrupts */ +static void gsi_isr_inter_ee_evt_ctrl(struct gsi *gsi) +{ + u32 event_mask; + + event_mask = ioread32(gsi->virt + GSI_INTER_EE_SRC_EV_CH_IRQ_OFFSET); + iowrite32(event_mask, + gsi->virt + GSI_INTER_EE_SRC_EV_CH_IRQ_CLR_OFFSET); + + while (event_mask) { + u32 evt_ring_id = __ffs(event_mask); + + event_mask ^= BIT(evt_ring_id); + + /* not currently expected */ + dev_err(gsi->dev, "evt %u inter-EE interrupt\n", evt_ring_id); + } +} + +/* We don't currently expect to receive any general event interrupts */ +static void gsi_isr_general(struct gsi *gsi) +{ + u32 val; + + val = ioread32(gsi->virt + GSI_CNTXT_GSI_IRQ_STTS_OFFSET); + iowrite32(val, gsi->virt + GSI_CNTXT_GSI_IRQ_CLR_OFFSET); + + if (val & CLR_BREAK_POINT_FMASK) + dev_err(gsi->dev, "breakpoint!\n"); + val ^= CLR_BREAK_POINT_FMASK; + + if (val) + dev_err(gsi->dev, "unexpected general interrupt 0x%08x\n", val); +} + +/** + * gsi_isr() - Top level GSI interrupt service routine + * @irq: Interrupt number (ignored) + * @dev_id: GSI pointer supplied to request_irq() + * + * This is the main handler function registered for the GSI IRQ. Each type + * of interrupt has a separate handler function that is called from here. + */ +static irqreturn_t gsi_isr(int irq, void *dev_id) +{ + struct gsi *gsi = dev_id; + u32 intr_mask; + u32 cnt = 0; + + while ((intr_mask = ioread32(gsi->virt + GSI_CNTXT_TYPE_IRQ_OFFSET))) { + /* intr_mask contains bitmask of pending GSI interrupts */ + do { + u32 gsi_intr = BIT(__ffs(intr_mask)); + + intr_mask ^= gsi_intr; + + switch (gsi_intr) { + 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_ieob(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: + dev_err(gsi->dev, + "%s: unrecognized type 0x%08x\n", + __func__, gsi_intr); + break; + } + } while (intr_mask); + + if (++cnt > GSI_ISR_MAX_ITER) { + dev_err(gsi->dev, "interrupt flood\n"); + break; + } + } + + return IRQ_HANDLED; +} + +/* Return the virtual address associated with a ring index */ +void *gsi_ring_virt(struct gsi_ring *ring, u32 index) +{ + /* Note: index *must* be used modulo the ring count here */ + return ring->virt + (index % ring->count) * sizeof(struct gsi_tre); +} + +/* Return the 32-bit DMA address associated with a ring index */ +u32 gsi_ring_addr(struct gsi_ring *ring, u32 index) +{ + return (ring->addr & GENMASK(31, 0)) + index * sizeof(struct gsi_tre); +} + +/* Return the ring index of a 32-bit ring offset */ +static u32 gsi_ring_index(struct gsi_ring *ring, u32 offset) +{ + /* Code assumes channel and event ring elements are the same size */ + BUILD_BUG_ON(sizeof(struct gsi_tre) != + sizeof(struct gsi_xfer_compl_evt)); + + return (offset - gsi_ring_addr(ring, 0)) / sizeof(struct gsi_tre); +} + +/* Return the transaction associated with a transfer completion event */ +static struct gsi_trans *gsi_event_trans(struct gsi_channel *channel, + struct gsi_xfer_compl_evt *evt) +{ + u32 tre_offset; + u32 tre_index; + + /* Event xfer_ptr records the TRE it's associated with */ + tre_offset = le64_to_cpu(evt->xfer_ptr) & GENMASK(31, 0); + tre_index = gsi_ring_index(&channel->tre_ring, tre_offset); + + return gsi_channel_trans_mapped(channel, tre_index); +} +/** + * gsi_channel_tx_update() - Report completed TX transfers + * @channel: Channel that has completed transmitting packets + * @trans: Last transation known to be complete + * + * Compute the number of transactions and bytes that have been + * transferred on a TX channel, and report that to higher layers in + * the network stack for throttling. + */ +static void +gsi_channel_tx_update(struct gsi_channel *channel, struct gsi_trans *trans) +{ + u64 byte_count = trans->byte_count + trans->len; + u64 trans_count = trans->trans_count + 1; + + byte_count -= channel->compl_byte_count; + channel->compl_byte_count += byte_count; + trans_count -= channel->compl_trans_count; + channel->compl_trans_count += trans_count;; + /* assert(trans_count <= U32_MAX); */ + + ipa_gsi_channel_tx_completed(channel->gsi, gsi_channel_id(channel), + trans_count, byte_count); +} + +/** + * gsi_evt_ring_rx_update() - Record lengths of received data + * @evt_ring: Event ring associated with channel that received packets + * @index: Event index in ring reported by hardware + * + * Events for RX channels contain the actual number of bytes received into + * the buffer. Every event has a transaction associated with it, and here + * we update transactions to record their actual received lengths. + * + * This function is called whenever we learn that the GSI hardware has filled + * new events since the last time we checked. The ring's index field tells + * the first entry in need of processing. The index provided is the + * first *unfilled* event in the ring (following the last filled one). + * + * Events are sequential within the event ring, and transactions are + * sequential within the transaction pool. + * + * Note that @index always refers to an element *within* the event ring. + */ +static void gsi_evt_ring_rx_update(struct gsi_evt_ring *evt_ring, u32 index) +{ + struct gsi_channel *channel = evt_ring->channel; + struct gsi_ring *ring = &evt_ring->ring; + struct gsi_xfer_compl_evt *evt_done; + struct gsi_trans_info *trans_info; + struct gsi_xfer_compl_evt *evt; + struct gsi_trans *trans; + u32 byte_count = 0; + u32 trans_avail; + u32 old_index; + u32 evt_avail; + + /* We'll start with the oldest un-processed event. RX channels + * replenish receive buffers in single-TRE transactions, so we + * can just map that event to its transaction. + */ + old_index = ring->index; + evt = gsi_ring_virt(ring, old_index); + trans = gsi_event_trans(channel, evt); + + /* Compute the number of events to process before we wrap */ + evt_avail = ring->count - old_index % ring->count; + + /* And compute how many transactions to process before we wrap */ + trans_info = &channel->trans_info; + trans_avail = (u32)(&trans_info->pool[trans_info->pool_count] - trans); + + /* Finally, determine when we'll be done processing events */ + evt_done = gsi_ring_virt(ring, index); + do { + trans->len = __le16_to_cpu(evt->len); + byte_count += trans->result; + + if (--evt_avail) + evt++; + else + evt = gsi_ring_virt(ring, 0); + + if (--trans_avail) + trans++; + else + trans = &trans_info->pool[0]; + } while (evt != evt_done); + + /* We record RX bytes when they are received */ + channel->byte_count += byte_count; + channel->trans_count++; +} + +/* Ring an event ring doorbell, reporting the last entry processed by the AP. + * The index argument (modulo the ring count) is the first unfilled entry, so + * we supply one less than that with the doorbell. Update the event ring + * index field with the value provided. + */ +static void gsi_evt_ring_doorbell(struct gsi *gsi, u32 evt_ring_id, u32 index) +{ + struct gsi_ring *ring = &gsi->evt_ring[evt_ring_id].ring; + u32 val; + + ring->index = index; /* Next unused entry */ + + /* Note: index *must* be used modulo the ring count here */ + val = gsi_ring_addr(ring, (index - 1) % ring->count); + iowrite32(val, gsi->virt + GSI_EV_CH_E_DOORBELL_0_OFFSET(evt_ring_id)); +} + +/* Return the maximum number of channels the hardware supports */ +static u32 gsi_channel_max(struct gsi *gsi) +{ + u32 val = ioread32(gsi->virt + GSI_GSI_HW_PARAM_2_OFFSET); + + return u32_get_bits(val, NUM_CH_PER_EE_FMASK); +} + +/* Return the maximum number of event rings the hardware supports */ +static u32 gsi_evt_ring_max(struct gsi *gsi) +{ + u32 val = ioread32(gsi->virt + GSI_GSI_HW_PARAM_2_OFFSET); + + return u32_get_bits(val, NUM_EV_PER_EE_FMASK); +} + +/* Issue a GSI command by writing a value to a register, then wait for + * completion to be signaled. Reports an error if the command times out. + * (Timeout is not expected, and suggests broken hardware.) + */ +static void +gsi_command(struct gsi *gsi, u32 reg, u32 val, struct completion *completion) +{ + reinit_completion(completion); + + iowrite32(val, gsi->virt + reg); + if (!wait_for_completion_timeout(completion, GSI_CMD_TIMEOUT * HZ)) + dev_err(gsi->dev, "%s timeout reg 0x%08x val 0x%08x\n", + __func__, reg, val); +} + +/* Issue an event ring command and wait for it to complete */ +static void evt_ring_command(struct gsi *gsi, u32 evt_ring_id, + enum gsi_evt_ch_cmd_opcode op) +{ + struct completion *completion = &gsi->evt_ring[evt_ring_id].completion; + u32 val = 0; + + val |= u32_encode_bits(evt_ring_id, EV_CHID_FMASK); + val |= u32_encode_bits(op, EV_OPCODE_FMASK); + + gsi_command(gsi, GSI_EV_CH_CMD_OFFSET, val, completion); +} + +/* Issue a channel command and wait for it to complete */ +static void +gsi_channel_command(struct gsi_channel *channel, enum gsi_ch_cmd_opcode op) +{ + u32 channel_id = gsi_channel_id(channel); + u32 val = 0; + + val |= u32_encode_bits(channel_id, CH_CHID_FMASK); + val |= u32_encode_bits(op, CH_OPCODE_FMASK); + + gsi_command(channel->gsi, GSI_CH_CMD_OFFSET, val, &channel->completion); +} + +/* Initialize a ring, including allocating DMA memory for its entries */ +static int gsi_ring_alloc(struct gsi *gsi, struct gsi_ring *ring, u32 count) +{ + size_t size = count * sizeof(struct gsi_tre); + dma_addr_t addr; + + BUILD_BUG_ON(!is_power_of_2(sizeof(struct gsi_tre))); + + if (!count) + return -EINVAL; + + /* Hardware requires a 2^n ring size, with alignment equal to size */ + ring->virt = dma_alloc_coherent(gsi->dev, size, &addr, GFP_KERNEL); + if (ring->virt && addr % size) { + dma_free_coherent(gsi->dev, size, ring->virt, ring->addr); + dev_err(gsi->dev, "unable to alloc 0x%zx-aligned ring buffer\n", + size); + return -EINVAL; /* Not a good error value, but distinct */ + } else if (!ring->virt) { + return -ENOMEM; + } + ring->addr = addr; + ring->count = count; + + return 0; +} + +/* Free a previously-allocated ring */ +static void gsi_ring_free(struct gsi *gsi, struct gsi_ring *ring) +{ + size_t size = ring->count * sizeof(struct gsi_tre); + + dma_free_coherent(gsi->dev, size, ring->virt, ring->addr); +} + +/* Program an event ring for use */ +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]; + size_t size = evt_ring->ring.count * sizeof(struct gsi_tre); + u32 val = 0; + + BUILD_BUG_ON(sizeof(struct gsi_xfer_compl_evt) > + field_max(EV_ELEMENT_SIZE_FMASK)); + + val |= u32_encode_bits(GSI_EVT_CHTYPE_GPI_EV, EV_CHTYPE_FMASK); + val |= EV_INTYPE_FMASK; + val |= u32_encode_bits(sizeof(struct gsi_xfer_compl_evt), + EV_ELEMENT_SIZE_FMASK); + iowrite32(val, gsi->virt + GSI_EV_CH_E_CNTXT_0_OFFSET(evt_ring_id)); + + val = u32_encode_bits(size, EV_R_LENGTH_FMASK); + iowrite32(val, gsi->virt + GSI_EV_CH_E_CNTXT_1_OFFSET(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 = evt_ring->ring.addr & GENMASK(31, 0); + iowrite32(val, gsi->virt + GSI_EV_CH_E_CNTXT_2_OFFSET(evt_ring_id)); + + val = evt_ring->ring.addr >> 32; + iowrite32(val, gsi->virt + GSI_EV_CH_E_CNTXT_3_OFFSET(evt_ring_id)); + + /* Enable interrupt moderation by setting the moderation delay */ + val = u32_encode_bits(IPA_GSI_EVT_RING_INT_MODT, MODT_FMASK); + val |= u32_encode_bits(1, MODC_FMASK); /* comes from channel */ + iowrite32(val, gsi->virt + GSI_EV_CH_E_CNTXT_8_OFFSET(evt_ring_id)); + + /* No MSI write data, and MSI address high and low address is 0 */ + iowrite32(0, gsi->virt + GSI_EV_CH_E_CNTXT_9_OFFSET(evt_ring_id)); + iowrite32(0, gsi->virt + GSI_EV_CH_E_CNTXT_10_OFFSET(evt_ring_id)); + iowrite32(0, gsi->virt + GSI_EV_CH_E_CNTXT_11_OFFSET(evt_ring_id)); + + /* We don't need to get event read pointer updates */ + iowrite32(0, gsi->virt + GSI_EV_CH_E_CNTXT_12_OFFSET(evt_ring_id)); + iowrite32(0, gsi->virt + GSI_EV_CH_E_CNTXT_13_OFFSET(evt_ring_id)); +} + +/* Issue an allocation request to the hardware for an event ring */ +static int gsi_evt_ring_alloc_hw(struct gsi *gsi, u32 evt_ring_id) +{ + struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id]; + + evt_ring_command(gsi, evt_ring_id, GSI_EVT_ALLOCATE); + + if (evt_ring->state != GSI_EVT_RING_STATE_ALLOCATED) { + dev_err(gsi->dev, "evt_ring_id %u allocation bad state %u\n", + evt_ring_id, evt_ring->state); + return -EIO; + } + + gsi_evt_ring_program(gsi, evt_ring_id); + + /* Have the first event in the ring be the first one filled. */ + gsi_evt_ring_doorbell(gsi, evt_ring_id, 0); + + return 0; +} + +/* Issue a hardware de-allocation request for an (allocated) event ring */ +static void gsi_evt_ring_free_hw(struct gsi *gsi, u32 evt_ring_id) +{ + evt_ring_command(gsi, evt_ring_id, GSI_EVT_RESET); + + evt_ring_command(gsi, evt_ring_id, GSI_EVT_DE_ALLOC); +} + +/* Allocate an available event ring id */ +static int gsi_evt_ring_id_alloc(struct gsi *gsi) +{ + u32 evt_ring_id; + + if (gsi->event_bitmap == ~0U) + return -ENOSPC; + + evt_ring_id = ffz(gsi->event_bitmap); + gsi->event_bitmap |= BIT(evt_ring_id); + + return (int)evt_ring_id; +} + +/* Free a previously-allocated event ring id */ +static void gsi_evt_ring_id_free(struct gsi *gsi, u32 evt_ring_id) +{ + gsi->event_bitmap &= ~BIT(evt_ring_id); +} + +/* Ring a channel doorbell, reporting the first un-filled entry */ +void gsi_channel_doorbell(struct gsi_channel *channel) +{ + struct gsi_ring *tre_ring = &channel->tre_ring; + u32 channel_id = gsi_channel_id(channel); + struct gsi *gsi = channel->gsi; + u32 val; + + /* Note: index *must* be used modulo the ring count here */ + val = gsi_ring_addr(tre_ring, tre_ring->index % tre_ring->count); + iowrite32(val, gsi->virt + GSI_CH_C_DOORBELL_0_OFFSET(channel_id)); +} + +/* Consult hardware, move any newly completed transactions to completed list */ +static void gsi_channel_update(struct gsi_channel *channel) +{ + u32 evt_ring_id = channel->evt_ring_id; + struct gsi *gsi = channel->gsi; + struct gsi_evt_ring *evt_ring; + struct gsi_trans *trans; + struct gsi_ring *ring; + u32 offset; + u32 index; + + evt_ring = &gsi->evt_ring[evt_ring_id]; + ring = &evt_ring->ring; + + /* See if there's anything new to process; if not, we're done. Note + * that index always refers to an entry *within* the event ring. + */ + offset = GSI_EV_CH_E_CNTXT_4_OFFSET(evt_ring_id); + index = gsi_ring_index(ring, ioread32(gsi->virt + offset)); + if (index == ring->index % ring->count) + return; + + /* Get the transaction for the latest completed event. Take a + * reference to keep it from completing before we give the events + * for this and previous transactions back to the hardware. + */ + trans = gsi_event_trans(channel, gsi_ring_virt(ring, index - 1)); + refcount_inc(&trans->refcount); + + /* For RX channels, update each completed transaction with the number + * of bytes that were actually received. For TX channels, report + * the number of transactions and bytes this completion represents + * up the network stack. + */ + if (channel->toward_ipa) + gsi_channel_tx_update(channel, trans); + else + gsi_evt_ring_rx_update(evt_ring, index); + + gsi_trans_move_complete(trans); + + /* Tell the hardware we've handled these events */ + gsi_evt_ring_doorbell(channel->gsi, channel->evt_ring_id, index); + + gsi_trans_free(trans); +} + +/** + * gsi_channel_poll_one() - Return a single completed transaction on a channel + * @channel: Channel to be polled + * + * @Return: Transaction pointer, or null if none are available + * + * This function returns the first entry on a channel's completed transaction + * list. If that list is empty, the hardware is consulted to determine + * whether any new transactions have completed. If so, they're moved to the + * completed list and the new first entry is returned. If there are no more + * completed transactions, a null pointer is returned. + */ +static struct gsi_trans *gsi_channel_poll_one(struct gsi_channel *channel) +{ + struct gsi_trans *trans; + + /* Get the first transaction from the completed list */ + trans = gsi_channel_trans_complete(channel); + if (!trans) { + /* List is empty; see if there's more to do */ + gsi_channel_update(channel); + trans = gsi_channel_trans_complete(channel); + } + + if (trans) + gsi_trans_move_polled(trans); + + return trans; +} + +/** + * gsi_channel_poll() - NAPI poll function for a channel + * @napi: NAPI structure for the channel + * @budget: Budget supplied by NAPI core + + * @Return: Number of items polled (<= budget) + * + * Single transactions completed by hardware are polled until either + * the budget is exhausted, or there are no more. Each transaction + * polled is passed to gsi_trans_complete(), to perform remaining + * completion processing and retire/free the transaction. + */ +static int gsi_channel_poll(struct napi_struct *napi, int budget) +{ + struct gsi_channel *channel; + int count = 0; + + channel = container_of(napi, struct gsi_channel, napi); + while (count < budget) { + struct gsi_trans *trans; + + trans = gsi_channel_poll_one(channel); + if (!trans) + break; + gsi_trans_complete(trans); + } + + if (count < budget) { + napi_complete(&channel->napi); + gsi_irq_event_enable(channel->gsi, channel->evt_ring_id); + } + + return count; +} + +/* The event bitmap represents which event ids are available for allocation. + * Set bits are not available, clear bits can be used. This function + * initializes the map so all events supported by the hardware are available, + * then precludes any reserved events from being allocated. + */ +static u32 gsi_event_bitmap_init(u32 evt_ring_max) +{ + u32 event_bitmap = GENMASK(BITS_PER_LONG - 1, evt_ring_max); + + return event_bitmap | GENMASK(GSI_MHI_ER_END, GSI_MHI_ER_START); +} + +/* Setup function for event rings */ +static int gsi_evt_ring_setup(struct gsi *gsi) +{ + u32 evt_ring_max; + u32 evt_ring_id; + + evt_ring_max = gsi_evt_ring_max(gsi); + dev_dbg(gsi->dev, "evt_ring_max %u\n", evt_ring_max); + if (evt_ring_max != GSI_EVT_RING_MAX) + return -EIO; + + for (evt_ring_id = 0; evt_ring_id < GSI_EVT_RING_MAX; evt_ring_id++) { + struct gsi_evt_ring *evt_ring = &gsi->evt_ring[evt_ring_id]; + + evt_ring->state = gsi_evt_ring_state(gsi, evt_ring_id); + if (evt_ring->state != GSI_EVT_RING_STATE_NOT_ALLOCATED) + return -EIO; + } + + return 0; +} + +/* Inverse of gsi_evt_ring_setup() */ +static void gsi_evt_ring_teardown(struct gsi *gsi) +{ + /* Nothing to do */ +} + +/* Configure a channel's "scratch registers" for a particular protocol */ +static void gsi_channel_scratch_write(struct gsi_channel *channel) +{ + u32 channel_id = gsi_channel_id(channel); + union gsi_channel_scratch scr = { }; + struct gsi_gpi_channel_scratch *gpi; + struct gsi *gsi = channel->gsi; + u32 val; + + /* See comments above definition of gsi_gpi_channel_scratch */ + gpi = &scr.gpi; + gpi->max_outstanding_tre = channel->data->tlv_count * + sizeof(struct gsi_tre); + gpi->outstanding_threshold = 2 * sizeof(struct gsi_tre); + + val = scr.data.word1; + iowrite32(val, gsi->virt + GSI_CH_C_SCRATCH_0_OFFSET(channel_id)); + + val = scr.data.word2; + iowrite32(val, gsi->virt + GSI_CH_C_SCRATCH_1_OFFSET(channel_id)); + + val = scr.data.word3; + iowrite32(val, gsi->virt + GSI_CH_C_SCRATCH_2_OFFSET(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 = ioread32(gsi->virt + GSI_CH_C_SCRATCH_3_OFFSET(channel_id)); + val = (scr.data.word4 & GENMASK(31, 16)) | (val & GENMASK(15, 0)); + iowrite32(val, gsi->virt + GSI_CH_C_SCRATCH_3_OFFSET(channel_id)); +} + +/* Program a channel for use */ +static void gsi_channel_program(struct gsi_channel *channel, bool doorbell) +{ + size_t size = channel->tre_ring.count * sizeof(struct gsi_tre); + u32 channel_id = gsi_channel_id(channel); + struct gsi *gsi = channel->gsi; + u32 wrr_weight = 0; + u32 val = 0; + + BUILD_BUG_ON(sizeof(struct gsi_tre) > field_max(ELEMENT_SIZE_FMASK)); + + val |= u32_encode_bits(GSI_CHANNEL_PROTOCOL_GPI, CHTYPE_PROTOCOL_FMASK); + if (channel->toward_ipa) + val |= CHTYPE_DIR_FMASK; + val |= u32_encode_bits(channel->evt_ring_id, ERINDEX_FMASK); + val |= u32_encode_bits(sizeof(struct gsi_tre), ELEMENT_SIZE_FMASK); + iowrite32(val, gsi->virt + GSI_CH_C_CNTXT_0_OFFSET(channel_id)); + + val = u32_encode_bits(size, R_LENGTH_FMASK); + iowrite32(val, gsi->virt + GSI_CH_C_CNTXT_1_OFFSET(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->tre_ring.addr & GENMASK(31, 0); + iowrite32(val, gsi->virt + GSI_CH_C_CNTXT_2_OFFSET(channel_id)); + + val = channel->tre_ring.addr >> 32; + iowrite32(val, gsi->virt + GSI_CH_C_CNTXT_3_OFFSET(channel_id)); + + if (channel->data->wrr_priority) + wrr_weight = field_max(WRR_WEIGHT_FMASK); + val = u32_encode_bits(wrr_weight, WRR_WEIGHT_FMASK); + + /* Max prefetch is 1 segment (do not set MAX_PREFETCH_FMASK) */ + if (doorbell) + val |= USE_DB_ENG_FMASK; + iowrite32(val, gsi->virt + GSI_CH_C_QOS_OFFSET(channel_id)); +} + +/* Configure a channel; we configure all channels to use GPI protocol */ +static void gsi_channel_config(struct gsi_channel *channel, bool db_enable) +{ + /* Start at the first TRE entry each time we configure the channel */ + channel->tre_ring.index = 0; + gsi_channel_program(channel, db_enable); + gsi_channel_scratch_write(channel); +} + +/* Setup function for a single channel */ +static int gsi_channel_setup_one(struct gsi_channel *channel) +{ + u32 evt_ring_id = channel->evt_ring_id; + struct gsi *gsi = channel->gsi; + u32 val; + int ret; + + if (!gsi) + return 0; /* Ignore uninitialized channels */ + + channel->state = gsi_channel_state(channel); + if (channel->state != GSI_CHANNEL_STATE_NOT_ALLOCATED) + return -EIO; + + mutex_lock(&gsi->mutex); + + ret = gsi_evt_ring_alloc_hw(gsi, evt_ring_id); + if (ret) + goto err_mutex_unlock; + + gsi_channel_command(channel, GSI_CH_ALLOCATE); + gsi->channel_stats.allocate++; + + ret = channel->state == GSI_CHANNEL_STATE_ALLOCATED ? 0 : -EIO; + if (ret) + goto err_free_evt_ring; + + gsi_channel_config(channel, true); + + mutex_unlock(&gsi->mutex); + + if (channel->toward_ipa) + netif_tx_napi_add(&gsi->dummy_dev, &channel->napi, + gsi_channel_poll, NAPI_POLL_WEIGHT); + else + netif_napi_add(&gsi->dummy_dev, &channel->napi, + gsi_channel_poll, NAPI_POLL_WEIGHT); + + /* Enable the event interrupt (clear it first in case pending) */ + val = BIT(evt_ring_id); + iowrite32(val, gsi->virt + GSI_CNTXT_SRC_IEOB_IRQ_CLR_OFFSET); + gsi_irq_event_enable(gsi, evt_ring_id); + + return 0; + +err_free_evt_ring: + gsi_evt_ring_free_hw(gsi, evt_ring_id); +err_mutex_unlock: + mutex_unlock(&gsi->mutex); + + return ret; +} + +/* Inverse of gsi_channel_setup_one() */ +static void gsi_channel_teardown_one(struct gsi_channel *channel) +{ + u32 evt_ring_id = channel->evt_ring_id; + struct gsi *gsi = channel->gsi; + + if (!gsi) + return; + + gsi_irq_event_disable(gsi, evt_ring_id); + + netif_napi_del(&channel->napi); + + mutex_lock(&gsi->mutex); + + gsi_channel_command(channel, GSI_CH_DE_ALLOC); + gsi->channel_stats.free++; + + gsi_evt_ring_free_hw(gsi, evt_ring_id); + + mutex_unlock(&gsi->mutex); + + gsi_channel_trans_exit(channel); +} + +/* Setup function for channels */ +static int gsi_channel_setup(struct gsi *gsi) +{ + u32 channel_max; + u32 channel_id; + int ret; + + channel_max = gsi_channel_max(gsi); + dev_dbg(gsi->dev, "channel_max %u\n", channel_max); + if (channel_max != GSI_CHANNEL_MAX) + return -EIO; + + ret = gsi_evt_ring_setup(gsi); + if (ret) + return ret; + + gsi_irq_enable(gsi); + + for (channel_id = 0; channel_id < GSI_CHANNEL_MAX; channel_id++) { + ret = gsi_channel_setup_one(&gsi->channel[channel_id]); + if (ret) + goto err_unwind; + } + + return 0; + +err_unwind: + while (channel_id--) + gsi_channel_teardown_one(&gsi->channel[channel_id]); + gsi_irq_enable(gsi); + gsi_evt_ring_teardown(gsi); + + return ret; +} + +/* Inverse of gsi_channel_setup() */ +static void gsi_channel_teardown(struct gsi *gsi) +{ + u32 channel_id; + + for (channel_id = 0; channel_id < GSI_CHANNEL_MAX; channel_id++) { + struct gsi_channel *channel = &gsi->channel[channel_id]; + + gsi_channel_teardown_one(channel); + } + gsi_irq_disable(gsi); + gsi_evt_ring_teardown(gsi); +} + +/* Setup function for GSI. GSI firmware must be loaded and initialized */ +int gsi_setup(struct gsi *gsi) +{ + u32 val; + + /* Here is where we first touch the GSI hardware */ + val = ioread32(gsi->virt + GSI_GSI_STATUS_OFFSET); + if (!(val & ENABLED_FMASK)) { + dev_err(gsi->dev, "GSI has not been enabled\n"); + return -EIO; + } + + /* Initialize the error log */ + iowrite32(0, gsi->virt + GSI_ERROR_LOG_OFFSET); + + /* Writing 1 indicates IRQ interrupts; 0 would be MSI */ + iowrite32(1, gsi->virt + GSI_CNTXT_INTSET_OFFSET); + + return gsi_channel_setup(gsi); +} + +/* Inverse of gsi_setup() */ +void gsi_teardown(struct gsi *gsi) +{ + gsi_channel_teardown(gsi); +} + +/* Initialize a channel's event ring */ +static int gsi_channel_evt_ring_init(struct gsi_channel *channel) +{ + struct gsi *gsi = channel->gsi; + struct gsi_evt_ring *evt_ring; + int ret; + + ret = gsi_evt_ring_id_alloc(gsi); + if (ret < 0) + return ret; + channel->evt_ring_id = ret; + + evt_ring = &gsi->evt_ring[channel->evt_ring_id]; + evt_ring->channel = channel; + + ret = gsi_ring_alloc(gsi, &evt_ring->ring, channel->data->event_count); + if (ret) + goto err_free_evt_ring_id; + + return 0; + +err_free_evt_ring_id: + gsi_evt_ring_id_free(gsi, channel->evt_ring_id); + + return ret; +} + +/* Inverse of gsi_channel_evt_ring_init() */ +static void gsi_channel_evt_ring_exit(struct gsi_channel *channel) +{ + struct gsi *gsi = channel->gsi; + struct gsi_evt_ring *evt_ring; + + evt_ring = &gsi->evt_ring[channel->evt_ring_id]; + gsi_ring_free(gsi, &evt_ring->ring); + gsi_evt_ring_id_free(gsi, channel->evt_ring_id); +} + +/* Init function for event rings */ +static void gsi_evt_ring_init(struct gsi *gsi) +{ + u32 evt_ring_id; + + BUILD_BUG_ON(GSI_EVT_RING_MAX >= BITS_PER_LONG); + + gsi->event_bitmap = gsi_event_bitmap_init(GSI_EVT_RING_MAX); + gsi->event_enable_bitmap = 0; + for (evt_ring_id = 0; evt_ring_id < GSI_EVT_RING_MAX; evt_ring_id++) + init_completion(&gsi->evt_ring[evt_ring_id].completion); +} + +/* Inverse of gsi_evt_ring_init() */ +static void gsi_evt_ring_exit(struct gsi *gsi) +{ + /* Nothing to do */ +} + +/* Init function for a single channel */ +static int +gsi_channel_init_one(struct gsi *gsi, const struct gsi_ipa_endpoint_data *data) +{ + struct gsi_channel *channel; + int ret; + + if (data->ee_id != GSI_EE_AP) + return 0; /* Ignore non-AP channels */ + + if (data->channel_id >= GSI_CHANNEL_MAX) { + dev_err(gsi->dev, "bad channel id %u (must be less than %u)\n", + data->channel_id, GSI_CHANNEL_MAX); + return -EINVAL; + } + + /* The value 256 here is arbitrary, and much higher than expected */ + if (!data->channel.tlv_count || data->channel.tlv_count > 256) { + dev_err(gsi->dev, "bad tlv_count %u (must be 1..256)\n", + data->channel.tlv_count); + return -EINVAL; + } + + if (!is_power_of_2(data->channel.tre_count)) { + dev_err(gsi->dev, "bad tre_count %u (must be power of 2)\n", + data->channel.tre_count); + return -EINVAL; + } + + if (!is_power_of_2(data->channel.event_count)) { + dev_err(gsi->dev, "bad event_count %u (must be power of 2)\n", + data->channel.event_count); + return -EINVAL; + } + + channel = &gsi->channel[data->channel_id]; + memset(channel, 0, sizeof(*channel)); + + channel->gsi = gsi; + channel->toward_ipa = data->toward_ipa; + channel->data = &data->channel; + + init_completion(&channel->completion); + + ret = gsi_channel_evt_ring_init(channel); + if (ret) + return ret; + + ret = gsi_ring_alloc(gsi, &channel->tre_ring, channel->data->tre_count); + if (ret) + goto err_channel_evt_ring_exit; + + ret = gsi_channel_trans_init(channel); + if (ret) + goto err_ring_free; + + return 0; + +err_ring_free: + gsi_ring_free(gsi, &channel->tre_ring); +err_channel_evt_ring_exit: + gsi_channel_evt_ring_exit(channel); + + return ret; +} + +/* Inverse of gsi_channel_init_one() */ +static void gsi_channel_exit_one(struct gsi_channel *channel) +{ + gsi_channel_trans_exit(channel); + gsi_ring_free(channel->gsi, &channel->tre_ring); + gsi_channel_evt_ring_exit(channel); +} + +/* Init function for channels */ +static int gsi_channel_init(struct gsi *gsi, u32 data_count, + const struct gsi_ipa_endpoint_data *data) +{ + int ret = 0; + u32 i; + + gsi_evt_ring_init(gsi); + for (i = 0; i < data_count; i++) { + ret = gsi_channel_init_one(gsi, &data[i]); + if (ret) + break; + } + + return ret; +} + +/* Inverse of gsi_channel_init() */ +static void gsi_channel_exit(struct gsi *gsi) +{ + u32 channel_id; + + for (channel_id = 0; channel_id < GSI_CHANNEL_MAX; channel_id++) + gsi_channel_exit_one(&gsi->channel[channel_id]); + gsi_evt_ring_exit(gsi); +} + +/* Init function for GSI. GSI hardware does not need to be "ready" */ +int gsi_init(struct gsi *gsi, struct platform_device *pdev, u32 data_count, + const struct gsi_ipa_endpoint_data *data) +{ + struct resource *res; + resource_size_t size; + unsigned int irq; + int ret; + + gsi->dev = &pdev->dev; + + /* The GSI layer performs NAPI on all endpoints. NAPI requires a + * network device structure, but the GSI layer does not have one, + * so we must create a dummy network device for this purpose. + */ + init_dummy_netdev(&gsi->dummy_dev); + + /* Get GSI memory range and map it */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gsi"); + if (!res) + return -ENXIO; + + size = resource_size(res); + if (res->start > U32_MAX || size > U32_MAX - res->start) + return -EINVAL; + + gsi->virt = ioremap(res->start, size); + if (!gsi->virt) + return -ENOMEM; + + mutex_init(&gsi->mutex); + + ret = platform_get_irq_byname(pdev, "gsi"); + if (ret < 0) + goto err_unmap_virt; + irq = ret; + + ret = request_irq(irq, gsi_isr, 0, "gsi", gsi); + if (ret) + goto err_unmap_virt; + gsi->irq = irq; + + ret = enable_irq_wake(gsi->irq); + if (ret) + dev_err(gsi->dev, "error %d enabling gsi wake irq\n", ret); + gsi->irq_wake_enabled = ret ? 0 : 1; + + ret = gsi_channel_init(gsi, data_count, data); + if (ret) + goto err_mutex_destroy; + + return 0; + +err_mutex_destroy: + if (gsi->irq_wake_enabled) + (void)disable_irq_wake(gsi->irq); + free_irq(gsi->irq, gsi); + mutex_destroy(&gsi->mutex); +err_unmap_virt: + iounmap(gsi->virt); + + return ret; +} + +/* Inverse of gsi_init() */ +void gsi_exit(struct gsi *gsi) +{ + gsi_channel_exit(gsi); + + if (gsi->irq_wake_enabled) + (void)disable_irq_wake(gsi->irq); + free_irq(gsi->irq, gsi); + mutex_destroy(&gsi->mutex); + iounmap(gsi->virt); +} + +/* Returns the maximum number of pending transactions on a channel */ +u32 gsi_channel_trans_max(struct gsi *gsi, u32 channel_id) +{ + struct gsi_channel *channel = &gsi->channel[channel_id]; + + return channel->data->tre_count; +} + +/* Returns the maximum number of TREs in a single transaction for a channel */ +u32 gsi_channel_trans_tre_max(struct gsi *gsi, u32 channel_id) +{ + struct gsi_channel *channel = &gsi->channel[channel_id]; + + return channel->data->tlv_count; +} + +/* Wait for all transaction activity on a channel to complete */ +void gsi_channel_trans_quiesce(struct gsi *gsi, u32 channel_id) +{ + struct gsi_trans *trans; + + /* Get the last transaction, and wait for it to complete */ + trans = gsi_channel_trans_last(gsi, channel_id); + if (trans) { + wait_for_completion(&trans->completion); + gsi_trans_free(trans); + } +} + +/* Make a channel operational */ +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) { + dev_err(gsi->dev, "channel %u bad state %u\n", channel_id, + (u32)channel->state); + return -ENOTSUPP; + } + + napi_enable(&channel->napi); + + mutex_lock(&gsi->mutex); + + gsi_channel_command(channel, GSI_CH_START); + gsi->channel_stats.start++; + + mutex_unlock(&gsi->mutex); + + return 0; +} + +/* Stop an operational channel */ +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) { + dev_err(gsi->dev, "channel %u bad state %u\n", channel_id, + (u32)channel->state); + return -ENOTSUPP; + } + + gsi_channel_trans_quiesce(gsi, channel_id); + + mutex_lock(&gsi->mutex); + + gsi_channel_command(channel, GSI_CH_STOP); + gsi->channel_stats.stop++; + + mutex_unlock(&gsi->mutex); + + if (channel->state == GSI_CHANNEL_STATE_STOPPED) + ret = 0; + else if (channel->state == GSI_CHANNEL_STATE_STOP_IN_PROC) + ret = -EAGAIN; + else + ret = -EIO; + + if (!ret) + napi_disable(&channel->napi); + + return ret; +} + +/* Reset and reconfigure a GSI channel (possibly leaving doorbell disabled) */ +int gsi_channel_reset(struct gsi *gsi, u32 channel_id, bool db_enable) +{ + struct gsi_channel *channel = &gsi->channel[channel_id]; + + if (channel->state != GSI_CHANNEL_STATE_STOPPED) { + dev_err(gsi->dev, "channel %u bad state %u\n", channel_id, + (u32)channel->state); + return -ENOTSUPP; + } + + /* In case the reset follows stop, need to wait 1 msec */ + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + + mutex_lock(&gsi->mutex); + + gsi_channel_command(channel, GSI_CH_RESET); + gsi->channel_stats.reset++; + + /* workaround: reset RX channels again */ + if (!channel->toward_ipa) { + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + gsi_channel_command(channel, GSI_CH_RESET); + } + + gsi_channel_config(channel, db_enable); + + /* Cancel pending transactions before the channel is started again */ + gsi_channel_trans_cancel_pending(channel); + + mutex_unlock(&gsi->mutex); + + return 0; +} From patchwork Fri May 31 03:53:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Elder X-Patchwork-Id: 165501 Delivered-To: patch@linaro.org Received: by 2002:a92:9e1a:0:0:0:0:0 with SMTP id q26csp186815ili; Thu, 30 May 2019 20:55:01 -0700 (PDT) X-Google-Smtp-Source: APXvYqx+wyJBS/dp9c6NeFFHUuOZsAWOQ4kZlZoraAFLAzh6P+PpiO/YbX/wfYqV3aJoz+Q0gNwp X-Received: by 2002:a17:90a:2263:: with SMTP id c90mr6795980pje.9.1559274901419; Thu, 30 May 2019 20:55:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1559274901; cv=none; d=google.com; s=arc-20160816; b=YiFc8LrNkQzGVs9w0ecuubdinW9kKLBlzi3bHH9GclP1gpZ77x5i6MUdd2r43lnLiw SzYFpi/ctRwV6zYV1w2JNfBGRcNfA0QFjppCnpysMjL4SGw6xK7gWHzp21m74ssNSt8b noJCM73WhUl4KR7frFUu5hQNQDldTSMdr/IUH8jzUA+X/Eclvke2gEXbBghyfILd9J7e BRzn3MRBrYBdrzEKtBrfMHQUf6KqrFRBrMwEE3Im+O/UbHAmcIaSqd5hym98myBVH+BZ FXrCKsMeBYQRLRQpmOyXb7IkCM4t6dI9Nw2ilrkzzd/heNWi0Mv79WQpsdNOWNS0L6/o 6SKg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=7J80d6jDGrLdHzifPJ0VY7ucmc99kOnuWbfGNwYA58Y=; b=tQ/YEguyEhJkAXsVy3mv7J8RmrfjAfdIeN+erG9KykmGdH4cGdDL8DMrfuWJayOssr UvGKBWMJp9+8pe8S70UqqRRoJNE/eLQVfBJFcjWorUy3sCnJRhaZ0Ipb+XX57OwpxYPH gNueznI4cVwlvjCwsmULis+Eibg/hfoebS2OtnONZga41jVvbIvYgLiphfNnmmAt9V6e A2fwPQ7HkuDbhLdo0wkzxrULEtcj4yz8g4M5TyMIQL3MSGFU3B5MI1+hbtR/ex2hApmT I/Jf+LDjOMkgoNUkb9tkUA5ftu9UfJovQDtys8BRtB4aW6FTMJzXtb8p/Z549ZcF2Zhb lZyA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b="RLHVhF/S"; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id b4si4586744plz.225.2019.05.30.20.55.01; Thu, 30 May 2019 20:55:01 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b="RLHVhF/S"; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727133AbfEaDzA (ORCPT + 7 others); Thu, 30 May 2019 23:55:00 -0400 Received: from mail-io1-f67.google.com ([209.85.166.67]:36995 "EHLO mail-io1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726910AbfEaDyL (ORCPT ); Thu, 30 May 2019 23:54:11 -0400 Received: by mail-io1-f67.google.com with SMTP id e5so7036246iok.4 for ; Thu, 30 May 2019 20:54:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=7J80d6jDGrLdHzifPJ0VY7ucmc99kOnuWbfGNwYA58Y=; b=RLHVhF/SfM9tmoUTrmHx3ze5aJYIIe8t21/QRU3xsQBo/WeBTPW3NWHTf2qE2noqKM A+H7kliAEs8blGxJWwv4ErSRAyvAqdlpDRD3NboA1EOOet9W1FcP+iY/zq5dViCCxxi5 PjOJ0OYw60BNaSqpdo7xGBq6Tz9xQYTIYUSUQyre8mX4F5yUM6LVXtE/fwxn7jahhh/I MVO1mLsZUwe+/AAlWTaM7iWpPXXCXVBocX9EoNKNg6EAEs6KuKtxRF/qCOgjvCPMzVEE 8DF1WFGgQdVTv6tLPNpKpaNTcvATqgazIe7emrHRTE14t3GPxDWLy3DSEWZx5ZnHSjZK AjMw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=7J80d6jDGrLdHzifPJ0VY7ucmc99kOnuWbfGNwYA58Y=; b=XBOzqeU3fSE+KydQCN2iOB91/pT/S8SAgU3qw0RiZKqayXSf8K0VT4lGvjsV5sc9P6 eXQ/oPvjRuzAzcXAqZr+woHkFan7882fbbYe9k3FGoGhRJKSRLTJSscjDJHHUDvCm90t 49TiKuH3uZxGP5gnLhNhMKZDtHmDhb6mLKDnEfln7qtRCXlAUnBHwmNuPsYcMRMa6Rwc Fg/uE37BxnlRhA3AlcW9dVeCnvdTHwzxF6tXPVclxDDjOlDsL4Xjk4AeLLsaa61gn2hT TF6vpNF6wNNQvGCqM3FWMQwM7thPDyG49sX08yqerKHjNeSP0ILrVVdxJqfA/ZACCTVj DtUw== X-Gm-Message-State: APjAAAW0xdrqxdNVfFv5B7Kn1j/7URj1Jqs9aES9U1yi7XUQr6aczd9A /XgpzovoJyWMNsQ3Nf8W3k3yPg== X-Received: by 2002:a05:6602:2245:: with SMTP id o5mr3638781ioo.59.1559274849286; Thu, 30 May 2019 20:54:09 -0700 (PDT) Received: from localhost.localdomain (c-71-195-29-92.hsd1.mn.comcast.net. [71.195.29.92]) by smtp.gmail.com with ESMTPSA id q15sm1626947ioi.15.2019.05.30.20.54.08 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 30 May 2019 20:54:08 -0700 (PDT) From: Alex Elder To: davem@davemloft.net, arnd@arndb.de, bjorn.andersson@linaro.org, ilias.apalodimas@linaro.org Cc: evgreen@chromium.org, benchan@google.com, ejcaruso@google.com, cpratapa@codeaurora.org, syadagir@codeaurora.org, subashab@codeaurora.org, abhishek.esse@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-soc@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org Subject: [PATCH v2 10/17] soc: qcom: ipa: IPA endpoints Date: Thu, 30 May 2019 22:53:41 -0500 Message-Id: <20190531035348.7194-11-elder@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190531035348.7194-1-elder@linaro.org> References: <20190531035348.7194-1-elder@linaro.org> MIME-Version: 1.0 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org This patch includes the code implementing an IPA endpoint. This is the primary abstraction implemented by the IPA. An endpoint is one end of a network connection between two entities physically connected to the IPA. Specifically, the AP and the modem implement endpoints, and an (AP endpoint, modem endpoint) pair implements the transfer of network data in one direction between the AP and modem. Endpoints are built on top of GSI channels, but IPA endpoints represent the higher-level functionality that the IPA provides. Data can be sent through a GSI channel, but it is the IPA endpoint that represents what is on the "other end" to receive that data. Other functionality, including aggregation, checksum offload and (at some future date) IP routing and filtering are all associated with the IPA endpoint. Signed-off-by: Alex Elder --- drivers/net/ipa/ipa_endpoint.c | 1283 ++++++++++++++++++++++++++++++++ drivers/net/ipa/ipa_endpoint.h | 97 +++ 2 files changed, 1380 insertions(+) create mode 100644 drivers/net/ipa/ipa_endpoint.c create mode 100644 drivers/net/ipa/ipa_endpoint.h -- 2.20.1 diff --git a/drivers/net/ipa/ipa_endpoint.c b/drivers/net/ipa/ipa_endpoint.c new file mode 100644 index 000000000000..0185db35033d --- /dev/null +++ b/drivers/net/ipa/ipa_endpoint.c @@ -0,0 +1,1283 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2019 Linaro Ltd. + */ + +#include +#include +#include +#include +#include + +#include "gsi.h" +#include "gsi_trans.h" +#include "ipa.h" +#include "ipa_data.h" +#include "ipa_endpoint.h" +#include "ipa_cmd.h" +#include "ipa_mem.h" +#include "ipa_netdev.h" + +#define atomic_dec_not_zero(v) atomic_add_unless((v), -1, 0) + +#define IPA_REPLENISH_BATCH 16 + +#define IPA_RX_BUFFER_SIZE (PAGE_SIZE << IPA_RX_BUFFER_ORDER) +#define IPA_RX_BUFFER_ORDER 1 /* 8KB endpoint RX buffers (2 pages) */ + +/* The amount of RX buffer space consumed by standard skb overhead */ +#define IPA_RX_BUFFER_OVERHEAD (PAGE_SIZE - SKB_MAX_ORDER(NET_SKB_PAD, 0)) + +#define IPA_ENDPOINT_STOP_RETRY_MAX 10 +#define IPA_ENDPOINT_STOP_RX_SIZE 1 /* bytes */ + +#define IPA_ENDPOINT_RESET_AGGR_RETRY_MAX 3 +#define IPA_AGGR_TIME_LIMIT_DEFAULT 1 /* milliseconds */ + +/** enum ipa_status_opcode - status element opcode hardware values */ +enum ipa_status_opcode { + IPA_STATUS_OPCODE_PACKET = 0x01, + IPA_STATUS_OPCODE_NEW_FRAG_RULE = 0x02, + IPA_STATUS_OPCODE_DROPPED_PACKET = 0x04, + IPA_STATUS_OPCODE_SUSPENDED_PACKET = 0x08, + IPA_STATUS_OPCODE_LOG = 0x10, + IPA_STATUS_OPCODE_DCMP = 0x20, + IPA_STATUS_OPCODE_PACKET_2ND_PASS = 0x40, +}; + +/** enum ipa_status_exception - status element exception type */ +enum ipa_status_exception { + IPA_STATUS_EXCEPTION_NONE, + IPA_STATUS_EXCEPTION_DEAGGR, + IPA_STATUS_EXCEPTION_IPTYPE, + IPA_STATUS_EXCEPTION_PACKET_LENGTH, + IPA_STATUS_EXCEPTION_PACKET_THRESHOLD, + IPA_STATUS_EXCEPTION_FRAG_RULE_MISS, + IPA_STATUS_EXCEPTION_SW_FILT, + IPA_STATUS_EXCEPTION_NAT, + IPA_STATUS_EXCEPTION_IPV6CT, + IPA_STATUS_EXCEPTION_MAX, +}; + +/** + * struct ipa_status - Abstracted IPA status element + * @opcode: Status element type + * @exception: The first exception that took place + * @pkt_len: Payload length + * @dst_endpoint: Destination endpoint + * @metadata: 32-bit metadata value used by packet + * @rt_miss: Flag; if 1, indicates there was a routing rule miss + * + * Note that the hardware status element supplies additional information + * that is currently unused. + */ +struct ipa_status { + enum ipa_status_opcode opcode; + enum ipa_status_exception exception; + u32 pkt_len; + u32 dst_endpoint; + u32 metadata; + u32 rt_miss; +}; + +/* Field masks for struct ipa_status_raw structure fields */ + +#define IPA_STATUS_SRC_IDX_FMASK GENMASK(4, 0) + +#define IPA_STATUS_DST_IDX_FMASK GENMASK(4, 0) + +#define IPA_STATUS_FLAGS1_FLT_LOCAL_FMASK GENMASK(0, 0) +#define IPA_STATUS_FLAGS1_FLT_HASH_FMASK GENMASK(1, 1) +#define IPA_STATUS_FLAGS1_FLT_GLOBAL_FMASK GENMASK(2, 2) +#define IPA_STATUS_FLAGS1_FLT_RET_HDR_FMASK GENMASK(3, 3) +#define IPA_STATUS_FLAGS1_FLT_RULE_ID_FMASK GENMASK(13, 4) +#define IPA_STATUS_FLAGS1_RT_LOCAL_FMASK GENMASK(14, 14) +#define IPA_STATUS_FLAGS1_RT_HASH_FMASK GENMASK(15, 15) +#define IPA_STATUS_FLAGS1_UCP_FMASK GENMASK(16, 16) +#define IPA_STATUS_FLAGS1_RT_TBL_IDX_FMASK GENMASK(21, 17) +#define IPA_STATUS_FLAGS1_RT_RULE_ID_FMASK GENMASK(31, 22) + +#define IPA_STATUS_FLAGS2_NAT_HIT_FMASK GENMASK_ULL(0, 0) +#define IPA_STATUS_FLAGS2_NAT_ENTRY_IDX_FMASK GENMASK_ULL(13, 1) +#define IPA_STATUS_FLAGS2_NAT_TYPE_FMASK GENMASK_ULL(15, 14) +#define IPA_STATUS_FLAGS2_TAG_INFO_FMASK GENMASK_ULL(63, 16) + +#define IPA_STATUS_FLAGS3_SEQ_NUM_FMASK GENMASK(7, 0) +#define IPA_STATUS_FLAGS3_TOD_CTR_FMASK GENMASK(31, 8) + +#define IPA_STATUS_FLAGS4_HDR_LOCAL_FMASK GENMASK(0, 0) +#define IPA_STATUS_FLAGS4_HDR_OFFSET_FMASK GENMASK(10, 1) +#define IPA_STATUS_FLAGS4_FRAG_HIT_FMASK GENMASK(11, 11) +#define IPA_STATUS_FLAGS4_FRAG_RULE_FMASK GENMASK(15, 12) +#define IPA_STATUS_FLAGS4_HW_SPECIFIC_FMASK GENMASK(31, 16) + +/* Status element provided by hardware */ +struct ipa_status_raw { + u8 opcode; + u8 exception; + u16 mask; + u16 pkt_len; + u8 endp_src_idx; /* Only bottom 5 bits valid */ + u8 endp_dst_idx; /* Only bottom 5 bits valid */ + u32 metadata; + u32 flags1; + u64 flags2; + u32 flags3; + u32 flags4; +}; + +static void ipa_endpoint_replenish(struct ipa_endpoint *endpoint, bool add_one); + +/* suspend_delay represents suspend for RX, delay for TX endpoints */ +bool ipa_endpoint_init_ctrl(struct ipa_endpoint *endpoint, bool suspend_delay) +{ + u32 offset = IPA_REG_ENDP_INIT_CTRL_N_OFFSET(endpoint->endpoint_id); + u32 mask; + u32 val; + + mask = endpoint->toward_ipa ? ENDP_DELAY_FMASK : ENDP_SUSPEND_FMASK; + + val = ioread32(endpoint->ipa->reg_virt + offset); + if (suspend_delay == !!(val & mask)) + return false; /* Already set to desired state */ + + val ^= mask; + iowrite32(val, endpoint->ipa->reg_virt + offset); + + return true; +} + +static void ipa_endpoint_init_cfg(struct ipa_endpoint *endpoint) +{ + u32 offset = IPA_REG_ENDP_INIT_CFG_N_OFFSET(endpoint->endpoint_id); + u32 val = 0; + + /* FRAG_OFFLOAD_EN is 0 */ + if (endpoint->data->config.checksum) { + if (endpoint->toward_ipa) { + u32 checksum_offset; + + val |= u32_encode_bits(IPA_CS_OFFLOAD_UL, + CS_OFFLOAD_EN_FMASK); + /* Checksum header offset is in 4-byte units */ + checksum_offset = sizeof(struct rmnet_map_header); + checksum_offset /= sizeof(u32); + val |= u32_encode_bits(checksum_offset, + CS_METADATA_HDR_OFFSET_FMASK); + } else { + val |= u32_encode_bits(IPA_CS_OFFLOAD_DL, + CS_OFFLOAD_EN_FMASK); + } + } else { + val |= u32_encode_bits(IPA_CS_OFFLOAD_NONE, + CS_OFFLOAD_EN_FMASK); + } + /* CS_GEN_QMB_MASTER_SEL is 0 */ + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +static void ipa_endpoint_init_hdr(struct ipa_endpoint *endpoint) +{ + u32 offset = IPA_REG_ENDP_INIT_HDR_N_OFFSET(endpoint->endpoint_id); + u32 val = 0; + + if (endpoint->data->config.qmap) { + size_t header_size = sizeof(struct rmnet_map_header); + + if (endpoint->toward_ipa && endpoint->data->config.checksum) + header_size += sizeof(struct rmnet_map_ul_csum_header); + + val |= u32_encode_bits(header_size, HDR_LEN_FMASK); + /* metadata is the 4 byte rmnet_map header itself */ + val |= HDR_OFST_METADATA_VALID_FMASK; + val |= u32_encode_bits(0, HDR_OFST_METADATA_FMASK); + /* HDR_ADDITIONAL_CONST_LEN is 0; (IPA->AP only) */ + if (!endpoint->toward_ipa) { + u32 size_offset = offsetof(struct rmnet_map_header, + pkt_len); + + val |= HDR_OFST_PKT_SIZE_VALID_FMASK; + val |= u32_encode_bits(size_offset, + HDR_OFST_PKT_SIZE_FMASK); + } + /* HDR_A5_MUX is 0 */ + /* HDR_LEN_INC_DEAGG_HDR is 0 */ + /* HDR_METADATA_REG_VALID is 0; (AP->IPA only) */ + } + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +static void ipa_endpoint_init_hdr_ext(struct ipa_endpoint *endpoint) +{ + u32 offset = IPA_REG_ENDP_INIT_HDR_EXT_N_OFFSET(endpoint->endpoint_id); + u32 pad_align = endpoint->data->config.rx.pad_align; + u32 val = 0; + + val |= HDR_ENDIANNESS_FMASK; /* big endian */ + val |= HDR_TOTAL_LEN_OR_PAD_VALID_FMASK; + /* HDR_TOTAL_LEN_OR_PAD is 0 (pad, not total_len) */ + /* HDR_PAYLOAD_LEN_INC_PADDING is 0 */ + /* HDR_TOTAL_LEN_OR_PAD_OFFSET is 0 */ + if (!endpoint->toward_ipa) + val |= u32_encode_bits(pad_align, HDR_PAD_TO_ALIGNMENT_FMASK); + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +/** + * Generate a metadata mask value that will select only the mux_id + * field in an rmnet_map header structure. The mux_id is at offset + * 1 byte from the beginning of the structure, but the metadata + * value is treated as a 4-byte unit. So this mask must be computed + * with endianness in mind. Note that ipa_endpoint_init_hdr_metadata_mask() + * will convert this value to the proper byte order. + * + * Marked __always_inline because this is really computing a + * constant value. + */ +static __always_inline __be32 ipa_rmnet_mux_id_metadata_mask(void) +{ + size_t mux_id_offset = offsetof(struct rmnet_map_header, mux_id); + u32 mux_id_mask = 0; + u8 *bytes; + + bytes = (u8 *)&mux_id_mask; + bytes[mux_id_offset] = 0xff; /* mux_id is 1 byte */ + + return cpu_to_be32(mux_id_mask); +} + +static void ipa_endpoint_init_hdr_metadata_mask(struct ipa_endpoint *endpoint) +{ + u32 endpoint_id = endpoint->endpoint_id; + u32 val = 0; + u32 offset; + + offset = IPA_REG_ENDP_INIT_HDR_METADATA_MASK_N_OFFSET(endpoint_id); + + if (!endpoint->toward_ipa && endpoint->data->config.qmap) + val = ipa_rmnet_mux_id_metadata_mask(); + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +/* Compute the aggregation size value to use for a given buffer size */ +static u32 ipa_aggr_size_kb(u32 rx_buffer_size) +{ + BUILD_BUG_ON(IPA_RX_BUFFER_SIZE > + field_max(AGGR_BYTE_LIMIT_FMASK) * SZ_1K + + IPA_MTU + IPA_RX_BUFFER_OVERHEAD); + + /* Because we don't have the "hard byte limit" enabled, we + * need to make sure there's enough space in the buffer to + * receive a complete MTU (plus normal skb overhead) beyond + * the aggregated size limit we specify. + */ + rx_buffer_size -= IPA_MTU + IPA_RX_BUFFER_OVERHEAD; + + return rx_buffer_size / SZ_1K; +} + +static void ipa_endpoint_init_aggr(struct ipa_endpoint *endpoint) +{ + const struct ipa_endpoint_config_data *config = &endpoint->data->config; + u32 offset = IPA_REG_ENDP_INIT_AGGR_N_OFFSET(endpoint->endpoint_id); + u32 val = 0; + + if (config->aggregation) { + if (!endpoint->toward_ipa) { + u32 aggr_size = ipa_aggr_size_kb(IPA_RX_BUFFER_SIZE); + + val |= u32_encode_bits(IPA_ENABLE_AGGR, AGGR_EN_FMASK); + val |= u32_encode_bits(IPA_GENERIC, AGGR_TYPE_FMASK); + val |= u32_encode_bits(aggr_size, + AGGR_BYTE_LIMIT_FMASK); + val |= u32_encode_bits(IPA_AGGR_TIME_LIMIT_DEFAULT, + AGGR_TIME_LIMIT_FMASK); + val |= u32_encode_bits(0, AGGR_PKT_LIMIT_FMASK); + if (config->rx.aggr_close_eof) + val |= AGGR_SW_EOF_ACTIVE_FMASK; + /* AGGR_HARD_BYTE_LIMIT_ENABLE is 0 */ + } else { + val |= u32_encode_bits(IPA_ENABLE_DEAGGR, + AGGR_EN_FMASK); + val |= u32_encode_bits(IPA_QCMAP, AGGR_TYPE_FMASK); + /* other fields ignored */ + } + /* AGGR_FORCE_CLOSE is 0 */ + } else { + val |= u32_encode_bits(IPA_BYPASS_AGGR, AGGR_EN_FMASK); + /* other fields ignored */ + } + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +static void ipa_endpoint_init_mode(struct ipa_endpoint *endpoint) +{ + u32 offset = IPA_REG_ENDP_INIT_MODE_N_OFFSET(endpoint->endpoint_id); + u32 val = 0; + + if (endpoint->toward_ipa && endpoint->data->config.dma_mode) { + u32 dma_endpoint_id = endpoint->data->config.dma_endpoint; + + val |= u32_encode_bits(IPA_DMA, MODE_FMASK); + val |= u32_encode_bits(dma_endpoint_id, DEST_PIPE_INDEX_FMASK); + } else { + val |= u32_encode_bits(IPA_BASIC, MODE_FMASK); + } + /* Other bitfields unspecified (and 0) */ + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +static void ipa_endpoint_init_deaggr(struct ipa_endpoint *endpoint) +{ + u32 offset = IPA_REG_ENDP_INIT_DEAGGR_N_OFFSET(endpoint->endpoint_id); + u32 val = 0; + + /* DEAGGR_HDR_LEN is 0 */ + /* PACKET_OFFSET_VALID is 0 */ + /* PACKET_OFFSET_LOCATION is ignored (not valid) */ + /* MAX_PACKET_LEN is 0 (not enforced) */ + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +static void ipa_endpoint_init_seq(struct ipa_endpoint *endpoint) +{ + u32 offset = IPA_REG_ENDP_INIT_SEQ_N_OFFSET(endpoint->endpoint_id); + u32 seq_type = endpoint->data->seq_type; + u32 val = 0; + + val |= u32_encode_bits(seq_type & 0xf, HPS_SEQ_TYPE_FMASK); + val |= u32_encode_bits((seq_type >> 4) & 0xf, DPS_SEQ_TYPE_FMASK); + /* HPS_REP_SEQ_TYPE is 0 */ + /* DPS_REP_SEQ_TYPE is 0 */ + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +/* Complete transaction initiated in ipa_endpoint_skb_tx() */ +void ipa_endpoint_skb_tx_complete(struct gsi_trans *trans) +{ + struct sk_buff *skb = trans->data; + + dev_kfree_skb_any(skb); +} + +/** + * ipa_endpoint_skb_tx() - Transmit a socket buffer + * @endpoint: Endpoint pointer + * @skb: Socket buffer to send + * + * Returns: 0 if successful, or a negative error code + */ +int ipa_endpoint_skb_tx(struct ipa_endpoint *endpoint, struct sk_buff *skb) +{ + struct gsi_trans *trans; + u32 nr_frags; + int ret; + + /* Make sure source endpoint's TLV FIFO has enough entries to + * hold the linear portion of the skb and all its fragments. + * If not, see if we can linearize it before giving up. + */ + nr_frags = skb_shinfo(skb)->nr_frags; + if (1 + nr_frags > endpoint->trans_tre_max) { + if (skb_linearize(skb)) + return -ENOMEM; + nr_frags = 0; + } + + trans = gsi_channel_trans_alloc(&endpoint->ipa->gsi, + endpoint->channel_id, nr_frags + 1); + if (!trans) + return -EBUSY; + trans->data = skb; + + ret = skb_to_sgvec(skb, trans->sgl, 0, skb->len); + if (ret < 0) + goto err_trans_free; + trans->sgc = ret; + + ret = gsi_trans_commit(trans, !netdev_xmit_more()); + if (ret) + goto err_trans_free; + return 0; + +err_trans_free: + gsi_trans_free(trans); + + return -ENOMEM; +} + +static void ipa_endpoint_status(struct ipa_endpoint *endpoint) +{ + const struct ipa_endpoint_config_data *config = &endpoint->data->config; + enum ipa_endpoint_id endpoint_id = endpoint->endpoint_id; + u32 val = 0; + u32 offset; + + offset = IPA_REG_ENDP_STATUS_N_OFFSET(endpoint_id); + + if (endpoint->data->config.status_enable) { + val |= STATUS_EN_FMASK; + if (endpoint->toward_ipa) { + u32 status_endpoint_id = config->tx.status_endpoint; + + val |= u32_encode_bits(status_endpoint_id, + STATUS_ENDP_FMASK); + } + /* STATUS_LOCATION is 0 (status element precedes packet) */ + /* STATUS_PKT_SUPPRESS_FMASK */ + } + + iowrite32(val, endpoint->ipa->reg_virt + offset); +} + +static void ipa_endpoint_skb_copy(struct ipa_endpoint *endpoint, + void *data, u32 len, u32 extra) +{ + struct sk_buff *skb; + + skb = __dev_alloc_skb(len, GFP_ATOMIC); + if (skb) { + skb_put(skb, len); + memcpy(skb->data, data, len); + skb->truesize += extra; + } + + /* Now receive it, or drop it if there's no netdev */ + if (endpoint->netdev) + ipa_netdev_skb_rx(endpoint->netdev, skb); + else if (skb) + dev_kfree_skb_any(skb); +} + +static void ipa_endpoint_skb_build(struct ipa_endpoint *endpoint, + struct page *page, u32 len) +{ + struct sk_buff *skb; + + /* assert(len <= SKB_WITH_OVERHEAD(IPA_RX_BUFFER_SIZE-NET_SKB_PAD)); */ + skb = build_skb(page_address(page), IPA_RX_BUFFER_SIZE); + if (skb) { + /* Reserve the headroom and account for the data */ + skb_reserve(skb, NET_SKB_PAD); + skb_put(skb, len); + } + + /* Now receive it, or drop it if there's no netdev */ + if (endpoint->netdev) + ipa_netdev_skb_rx(endpoint->netdev, skb); + else if (skb) + dev_kfree_skb_any(skb); + + /* If no socket buffer took the pages, free them */ + if (!skb) + __free_pages(page, IPA_RX_BUFFER_ORDER); +} + +/* Maps an exception type returned in a ipa_status_raw structure + * to the ipa_status_exception value that represents it in + * the exception field of a ipa_status structure. Returns + * IPA_STATUS_EXCEPTION_MAX for an unrecognized value. + */ +static enum ipa_status_exception exception_map(u8 exception, bool is_ipv6) +{ + switch (exception) { + case 0x00: return IPA_STATUS_EXCEPTION_NONE; + case 0x01: return IPA_STATUS_EXCEPTION_DEAGGR; + case 0x04: return IPA_STATUS_EXCEPTION_IPTYPE; + case 0x08: return IPA_STATUS_EXCEPTION_PACKET_LENGTH; + case 0x10: return IPA_STATUS_EXCEPTION_FRAG_RULE_MISS; + case 0x20: return IPA_STATUS_EXCEPTION_SW_FILT; + case 0x40: return is_ipv6 ? IPA_STATUS_EXCEPTION_IPV6CT + : IPA_STATUS_EXCEPTION_NAT; + default: return IPA_STATUS_EXCEPTION_MAX; + } +} + +/* A rule miss is indicated as an all-1's value in the rt_rule_id + * or flt_rule_id field of the ipa_status structure. + */ +static bool ipa_rule_miss_id(u32 id) +{ + return id == field_max(IPA_STATUS_FLAGS1_RT_RULE_ID_FMASK); +} + +size_t ipa_status_parse(struct ipa_status *status, void *data, u32 count) +{ + const struct ipa_status_raw *status_raw = data; + bool is_ipv6; + u32 val; + + BUILD_BUG_ON(sizeof(*status_raw) % 4); + if (WARN_ON(count < sizeof(*status_raw))) + return 0; + + status->opcode = status_raw->opcode; + is_ipv6 = status_raw->mask & BIT(7) ? false : true; + status->exception = exception_map(status_raw->exception, is_ipv6); + status->pkt_len = status_raw->pkt_len; + val = u32_get_bits(status_raw->endp_dst_idx, IPA_STATUS_DST_IDX_FMASK); + status->dst_endpoint = val; + status->metadata = status_raw->metadata; + val = u32_get_bits(status_raw->flags1, + IPA_STATUS_FLAGS1_RT_RULE_ID_FMASK); + status->rt_miss = ipa_rule_miss_id(val) ? 1 : 0; + + return sizeof(*status_raw); +} + +/* The format of a packet status element is the same for several status + * types (opcodes). The NEW_FRAG_RULE, LOG, DCMP (decompression) types + * aren't currently supported + */ +static bool ipa_status_format_packet(enum ipa_status_opcode opcode) +{ + switch (opcode) { + case IPA_STATUS_OPCODE_PACKET: + case IPA_STATUS_OPCODE_DROPPED_PACKET: + case IPA_STATUS_OPCODE_SUSPENDED_PACKET: + case IPA_STATUS_OPCODE_PACKET_2ND_PASS: + return true; + default: + return false; + } +} + +static bool ipa_endpoint_status_skip(struct ipa_endpoint *endpoint, + struct ipa_status *status) +{ + if (!ipa_status_format_packet(status->opcode)) + return true; + if (!status->pkt_len) + return true; + if (status->dst_endpoint != endpoint->endpoint_id) + return true; + + return false; /* Don't skip this packet, process it */ +} + +static void ipa_endpoint_status_parse(struct ipa_endpoint *endpoint, + struct page *page, u32 total_len) +{ + void *data = page_address(page) + NET_SKB_PAD; + u32 unused = IPA_RX_BUFFER_SIZE - total_len; + u32 resid = total_len; + + while (resid) { + struct ipa_status status; + bool drop_packet = false; + size_t status_size; + u32 align; + u32 len; + + status_size = ipa_status_parse(&status, data, resid); + + /* Skip over status packets that lack packet data */ + if (ipa_endpoint_status_skip(endpoint, &status)) { + data += status_size; + resid -= status_size; + continue; + } + + /* Packet data follows the status structure. Unless + * the packet failed to match a routing rule, or it + * had a deaggregation exception, we'll consume it. + */ + if (status.exception == IPA_STATUS_EXCEPTION_NONE) { + if (status.rt_miss) + drop_packet = true; + } else if (status.exception == IPA_STATUS_EXCEPTION_DEAGGR) { + drop_packet = true; + } + + /* Compute the amount of buffer space consumed by the + * packet, including the status element. If the hardware + * is configured to pad packet data to an aligned boundary, + * account for that. And if checksum offload is is enabled + * a trailer containing computed checksum information will + * be appended. + */ + align = endpoint->data->config.rx.pad_align ? : 1; + len = status_size + ALIGN(status.pkt_len, align); + if (endpoint->data->config.checksum) + len += sizeof(struct rmnet_map_dl_csum_trailer); + + /* Charge the new packet with a proportional fraction of + * the unused space in the original receive buffer. + * XXX Charge a proportion of the *whole* receive buffer? + */ + if (!drop_packet) { + u32 extra = unused * len / total_len; + void *data2 = data + status_size; + u32 len2 = status.pkt_len; + + /* Client receives only packet data (no status) */ + ipa_endpoint_skb_copy(endpoint, data2, len2, extra); + } + + /* Consume status and the full packet it describes */ + data += len; + resid -= len; + } + + __free_pages(page, IPA_RX_BUFFER_ORDER); +} + +/* Complete transaction initiated in ipa_endpoint_replenish_one() */ +void ipa_endpoint_rx_complete(struct gsi_trans *trans) +{ + struct page *page = trans->data; + struct ipa_endpoint *endpoint; + struct ipa *ipa; + + ipa = container_of(trans->gsi, struct ipa, gsi); + endpoint = ipa->endpoint_map[trans->channel_id]; + + ipa_endpoint_replenish(endpoint, true); + + if (trans->result == -ECANCELED) { + __free_pages(page, IPA_RX_BUFFER_ORDER); + return; + } + + /* Parse or build a socket buffer using the actual received length */ + if (endpoint->data->config.status_enable) + ipa_endpoint_status_parse(endpoint, page, trans->len); + else + ipa_endpoint_skb_build(endpoint, page, trans->len); +} + +static int ipa_endpoint_replenish_one(struct ipa_endpoint *endpoint) +{ + struct gsi_trans *trans; + bool doorbell = false; + struct page *page; + u32 offset; + u32 len; + + page = dev_alloc_pages(IPA_RX_BUFFER_ORDER); + if (!page) + return -ENOMEM; + offset = NET_SKB_PAD; + len = IPA_RX_BUFFER_SIZE - offset; + + trans = gsi_channel_trans_alloc(&endpoint->ipa->gsi, + endpoint->channel_id, 1); + if (!trans) + goto err_page_free; + trans->data = page; + + /* Set up and map a scatterlist entry representing the buffer */ + sg_init_table(trans->sgl, trans->sgc); + sg_set_page(trans->sgl, page, len, offset); + + if (++endpoint->replenish_ready == IPA_REPLENISH_BATCH) { + doorbell = true; + endpoint->replenish_ready = 0; + } + + if (!gsi_trans_commit(trans, doorbell)) + return 0; + +err_page_free: + __free_pages(page, IPA_RX_BUFFER_ORDER); + + return -ENOMEM; +} + +/** + * ipa_endpoint_replenish() - Replenish the Rx packets cache. + * + * 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_endpoint_replenish(struct ipa_endpoint *endpoint, bool add_one) +{ + struct gsi *gsi; + u32 backlog; + + if (add_one) { + if (endpoint->replenish_enabled) + atomic_inc(&endpoint->replenish_backlog); + else + atomic_inc(&endpoint->replenish_saved); + } + + if (!endpoint->replenish_enabled) + return; + + while (atomic_dec_not_zero(&endpoint->replenish_backlog)) + if (ipa_endpoint_replenish_one(endpoint)) + goto try_again_later; + + return; + +try_again_later: + /* The last one didn't succeed, so fix the backlog */ + backlog = atomic_inc_return(&endpoint->replenish_backlog); + + /* Whenever a receive buffer transaction completes we'll try to + * replenish again. It's unlikely, but if we fail to supply even + * one buffer, nothing will trigger another replenish attempt. + * If this happens, schedule work to try again. + */ + gsi = &endpoint->ipa->gsi; + if (backlog == gsi_channel_trans_max(gsi, endpoint->channel_id)) + schedule_delayed_work(&endpoint->replenish_work, + msecs_to_jiffies(1)); +} + +static void ipa_endpoint_replenish_enable(struct ipa_endpoint *endpoint) +{ + struct gsi *gsi = &endpoint->ipa->gsi; + u32 max_backlog; + u32 saved; + + endpoint->replenish_enabled = true; + while ((saved = atomic_xchg(&endpoint->replenish_saved, 0))) + atomic_add(saved, &endpoint->replenish_backlog); + + /* Start replenishing if hardware currently has no buffers */ + max_backlog = gsi_channel_trans_max(gsi, endpoint->channel_id); + if (atomic_read(&endpoint->replenish_backlog) == max_backlog) { + ipa_endpoint_replenish(endpoint, false); + return; + } +} + +static void ipa_endpoint_replenish_disable(struct ipa_endpoint *endpoint) +{ + endpoint->replenish_enabled = false; +} + +static void ipa_endpoint_replenish_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ipa_endpoint *endpoint; + + endpoint = container_of(dwork, struct ipa_endpoint, replenish_work); + + ipa_endpoint_replenish(endpoint, false); +} + +static bool ipa_endpoint_set_up(struct ipa_endpoint *endpoint) +{ + struct ipa *ipa = endpoint->ipa; + + return ipa && (ipa->set_up & BIT(endpoint->endpoint_id)); +} + +static void ipa_endpoint_default_route_set(struct ipa *ipa, + enum ipa_endpoint_id endpoint_id) +{ + u32 val; + + /* ROUTE_DIS is 0 */ + val = u32_encode_bits(endpoint_id, ROUTE_DEF_PIPE_FMASK); + val |= ROUTE_DEF_HDR_TABLE_FMASK; + val |= u32_encode_bits(0, ROUTE_DEF_HDR_OFST_FMASK); + val |= u32_encode_bits(endpoint_id, ROUTE_FRAG_DEF_PIPE_FMASK); + val |= ROUTE_DEF_RETAIN_HDR_FMASK; + + iowrite32(val, ipa->reg_virt + IPA_REG_ROUTE_OFFSET); +} +/** + * ipa_endpoint_default_route_init() - Configure IPA default route + * @ipa: IPA pointer + * @client: Client to which exceptions should be directed + */ +void ipa_endpoint_default_route_setup(struct ipa_endpoint *endpoint) +{ + ipa_endpoint_default_route_set(endpoint->ipa, endpoint->endpoint_id); +} + +/** + * ipa_endpoint_default_route_teardown() - + * Inverse of ipa_endpoint_default_route_setup() + * @ipa: IPA pointer + */ +void ipa_endpoint_default_route_teardown(struct ipa_endpoint *endpoint) +{ + ipa_endpoint_default_route_set(endpoint->ipa, 0); +} + +/** + * ipa_endpoint_stop()- Stops a GSI channel in IPA + * @client: Client whose endpoint should be stopped + * + * 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_endpoint_stop(struct ipa_endpoint *endpoint) +{ + struct device *dev = &endpoint->ipa->pdev->dev; + size_t size = IPA_ENDPOINT_STOP_RX_SIZE; + struct gsi *gsi = &endpoint->ipa->gsi; + void *virt = NULL; + dma_addr_t addr; + int ret; + int i; + + /* An RX endpoint might not stop right away. In that case we issue + * a small (1-byte) DMA command, delay for a bit (1-2 milliseconds), + * and try again. Allocate the DMA buffer in case this is needed. + */ + if (!endpoint->toward_ipa) { + virt = dma_alloc_coherent(dev, size, &addr, GFP_KERNEL); + if (!virt) + return -ENOMEM; + } + + for (i = 0; i < IPA_ENDPOINT_STOP_RETRY_MAX; i++) { + ret = gsi_channel_stop(gsi, endpoint->channel_id); + if (ret != -EAGAIN) + break; + + if (endpoint->toward_ipa) + continue; + + /* Send a 1 byte 32-bit DMA task and try again after a delay */ + ret = ipa_cmd_dma_task_32(endpoint->ipa, size, addr); + if (ret) + break; + + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + } + if (i >= IPA_ENDPOINT_STOP_RETRY_MAX) + ret = -EIO; + + if (!endpoint->toward_ipa) + dma_free_coherent(dev, size, virt, addr); + + return ret; +} + +bool ipa_endpoint_enabled(struct ipa_endpoint *endpoint) +{ + return !!(endpoint->ipa->enabled & BIT(endpoint->endpoint_id)); +} + +int ipa_endpoint_enable_one(struct ipa_endpoint *endpoint) +{ + struct ipa *ipa = endpoint->ipa; + int ret; + + if (WARN_ON(!ipa_endpoint_set_up(endpoint))) + return -EINVAL; + + ret = gsi_channel_start(&ipa->gsi, endpoint->channel_id); + if (ret) + return ret; + + ipa_interrupt_suspend_enable(ipa->interrupt, endpoint->endpoint_id); + + if (!endpoint->toward_ipa) + ipa_endpoint_replenish_enable(endpoint); + + ipa->enabled |= BIT(endpoint->endpoint_id); + + return 0; +} + +void ipa_endpoint_disable_one(struct ipa_endpoint *endpoint) +{ + struct ipa *ipa = endpoint->ipa; + int ret; + + if (WARN_ON(!ipa_endpoint_enabled(endpoint))) + return; + + if (!endpoint->toward_ipa) + ipa_endpoint_replenish_disable(endpoint); + + ipa_interrupt_suspend_disable(ipa->interrupt, endpoint->endpoint_id); + + ret = ipa_endpoint_stop(endpoint); + WARN(ret, "error %d attempting to stop endpoint %u\n", ret, + endpoint->endpoint_id); + + if (!ret) + endpoint->ipa->enabled &= ~BIT(endpoint->endpoint_id); +} + +static bool ipa_endpoint_aggr_active(struct ipa_endpoint *endpoint) +{ + u32 mask = BIT(endpoint->endpoint_id); + struct ipa *ipa = endpoint->ipa; + u32 val; + + val = ioread32(ipa->reg_virt + IPA_REG_STATE_AGGR_ACTIVE_OFFSET); + + return !!(val & mask); +} + +static void ipa_endpoint_force_close(struct ipa_endpoint *endpoint) +{ + u32 mask = BIT(endpoint->endpoint_id); + struct ipa *ipa = endpoint->ipa; + u32 val; + + val = u32_encode_bits(mask, PIPE_BITMAP_FMASK); + iowrite32(val, ipa->reg_virt + IPA_REG_AGGR_FORCE_CLOSE_OFFSET); +} + +/** + * ipa_endpoint_reset_rx_aggr() - Reset RX endpoint with aggregation active + * @endpoint: Endpoint to be reset + * + * If aggregation is active on an RX endpoint when a reset is performed + * on its underlying GSI channel, a special sequence of actions must be + * taken to ensure the IPA pipeline is properly cleared. + * + * @Return: 0 if successful, or a negative error code + */ +static int ipa_endpoint_reset_rx_aggr(struct ipa_endpoint *endpoint) +{ + struct device *dev = &endpoint->ipa->pdev->dev; + struct ipa *ipa = endpoint->ipa; + bool endpoint_suspended = false; + struct gsi *gsi = &ipa->gsi; + dma_addr_t addr; + u32 len = 1; + void *virt; + int ret; + int i; + + virt = kzalloc(len, GFP_KERNEL); + if (!virt) + return -ENOMEM; + + addr = dma_map_single(dev, virt, len, DMA_FROM_DEVICE); + if (dma_mapping_error(dev, addr)) { + ret = -ENOMEM; + goto out_free_virt; + } + + /* Force close aggregation before issuing the reset */ + ipa_endpoint_force_close(endpoint); + + /* Reset and reconfigure the channel with the doorbell engine + * disabled. Then poll until we know aggregation is no longer + * active. We'll re-enable the doorbell when we reset below. + */ + ret = gsi_channel_reset(gsi, endpoint->channel_id, false); + if (ret) + goto out_unmap_addr; + + if (ipa_endpoint_init_ctrl(endpoint, false)) + endpoint_suspended = true; + + /* Start channel and do a 1 byte read */ + ret = gsi_channel_start(gsi, endpoint->channel_id); + if (ret) + goto out_suspend_again; + + ret = gsi_trans_read_byte(gsi, endpoint->channel_id, addr); + if (ret) + goto err_stop_channel; + + /* Wait for aggregation to be closed on the channel */ + for (i = 0; i < IPA_ENDPOINT_RESET_AGGR_RETRY_MAX; i++) { + if (!ipa_endpoint_aggr_active(endpoint)) + break; + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + } + WARN_ON(ipa_endpoint_aggr_active(endpoint)); + gsi_trans_read_byte_done(gsi, endpoint->channel_id); + + ret = ipa_endpoint_stop(endpoint); + if (ret) + goto out_suspend_again; + + /* Finally, reset and reconfigure the channel again (this time with + * the doorbell engine enabled). Sleep for 1 millisecond to complete + * the channel reset sequence. Finish by suspending the channel + * again (if necessary). + */ + ret = gsi_channel_reset(gsi, endpoint->channel_id, true); + + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + + goto out_suspend_again; + +err_stop_channel: + ipa_endpoint_stop(endpoint); +out_suspend_again: + if (endpoint_suspended) + (void)ipa_endpoint_init_ctrl(endpoint, true); +out_unmap_addr: + dma_unmap_single(dev, addr, len, DMA_FROM_DEVICE); +out_free_virt: + kfree(virt); + + return ret; +} + +static void ipa_endpoint_reset(struct ipa_endpoint *endpoint) +{ + u32 channel_id = endpoint->channel_id; + struct ipa *ipa = endpoint->ipa; + struct gsi *gsi = &ipa->gsi; + int ret; + + /* For TX endpoints, or RX endpoints without aggregation active, + * we only need to reset the underlying GSI channel. + */ + if (!endpoint->toward_ipa && endpoint->data->config.aggregation) { + if (ipa_endpoint_aggr_active(endpoint)) + ret = ipa_endpoint_reset_rx_aggr(endpoint); + else + ret = gsi_channel_reset(gsi, channel_id, true); + } else { + ret = gsi_channel_reset(gsi, channel_id, true); + } + WARN(ret, "error %d attempting to reset channel %u\n", ret, + endpoint->channel_id); +} + +static bool ipa_endpoint_suspended(struct ipa_endpoint *endpoint) +{ + return !!(endpoint->ipa->suspended & BIT(endpoint->endpoint_id)); +} + +/** + * ipa_endpoint_suspend_aggr() - Emulate suspend interrupt + * @endpoint_id: Endpoint on which to emulate a suspend + * + * Emulate suspend IPA interrupt to unsuspend an endpoint suspended + * with an open aggregation frame. This is to work around a hardware + * issue where the suspend interrupt will not be generated when it + * should be. + */ +static void ipa_endpoint_suspend_aggr(struct ipa_endpoint *endpoint) +{ + struct ipa *ipa = endpoint->ipa; + + /* Nothing to do if the endpoint doesn't have aggregation open */ + if (!ipa_endpoint_aggr_active(endpoint)) + return; + + /* Force close aggregation */ + ipa_endpoint_force_close(endpoint); + + ipa_interrupt_simulate_suspend(ipa->interrupt); +} + +void ipa_endpoint_suspend(struct ipa_endpoint *endpoint) +{ + struct gsi *gsi = &endpoint->ipa->gsi; + + if (!ipa_endpoint_enabled(endpoint)) + return; + + if (!endpoint->toward_ipa) { + if (!ipa_endpoint_init_ctrl(endpoint, true)) + return; + + ipa_endpoint_replenish_disable(endpoint); + + /* 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. + */ + if (endpoint->data->config.aggregation) + ipa_endpoint_suspend_aggr(endpoint); + } + + gsi_channel_trans_quiesce(gsi, endpoint->channel_id); + + endpoint->ipa->suspended |= BIT(endpoint->endpoint_id); +} + +void ipa_endpoint_resume(struct ipa_endpoint *endpoint) +{ + if (!ipa_endpoint_suspended(endpoint)) + return; + + if (endpoint->toward_ipa) + ipa_endpoint_replenish_enable(endpoint); + else + WARN_ON(ipa_endpoint_init_ctrl(endpoint, false)); + + endpoint->ipa->suspended &= ~BIT(endpoint->endpoint_id); +} + +static void ipa_endpoint_program(struct ipa_endpoint *endpoint) +{ + if (endpoint->toward_ipa) { + bool delay_mode = !!endpoint->data->config.tx.delay; + + (void)ipa_endpoint_init_ctrl(endpoint, delay_mode); + ipa_endpoint_init_hdr_ext(endpoint); + ipa_endpoint_init_aggr(endpoint); + ipa_endpoint_init_deaggr(endpoint); + ipa_endpoint_init_seq(endpoint); + } else { + (void)ipa_endpoint_init_ctrl(endpoint, false); + ipa_endpoint_init_hdr_ext(endpoint); + ipa_endpoint_init_aggr(endpoint); + } + ipa_endpoint_init_cfg(endpoint); + ipa_endpoint_init_hdr(endpoint); + ipa_endpoint_init_hdr_metadata_mask(endpoint); + ipa_endpoint_init_mode(endpoint); + ipa_endpoint_status(endpoint); +} + +static void ipa_endpoint_setup_one(struct ipa_endpoint *endpoint) +{ + struct gsi *gsi = &endpoint->ipa->gsi; + u32 channel_id = endpoint->channel_id; + + /* Only AP endpoints get configured */ + if (endpoint->ee_id != GSI_EE_AP) + return; + + endpoint->trans_tre_max = gsi_channel_trans_tre_max(gsi, channel_id); + if (!endpoint->toward_ipa) { + endpoint->replenish_enabled = false; + atomic_set(&endpoint->replenish_saved, + gsi_channel_trans_max(gsi, endpoint->channel_id)); + atomic_set(&endpoint->replenish_backlog, 0); + INIT_DELAYED_WORK(&endpoint->replenish_work, + ipa_endpoint_replenish_work); + } + + ipa_endpoint_program(endpoint); + + endpoint->ipa->set_up |= BIT(endpoint->endpoint_id); +} + +static void ipa_endpoint_teardown_one(struct ipa_endpoint *endpoint) +{ + if (!endpoint->toward_ipa) + cancel_delayed_work_sync(&endpoint->replenish_work); + + ipa_endpoint_reset(endpoint); + + endpoint->ipa->set_up &= ~BIT(endpoint->endpoint_id); +} + +void ipa_endpoint_setup(struct ipa *ipa) +{ + u32 initialized = ipa->initialized; + + ipa->set_up = 0; + while (initialized) { + enum ipa_endpoint_id endpoint_id = __ffs(initialized); + + initialized ^= BIT(endpoint_id); + + ipa_endpoint_setup_one(&ipa->endpoint[endpoint_id]); + } +} + +void ipa_endpoint_teardown(struct ipa *ipa) +{ + u32 set_up = ipa->set_up; + + while (set_up) { + enum ipa_endpoint_id endpoint_id = __fls(set_up); + + set_up ^= BIT(endpoint_id); + + ipa_endpoint_teardown_one(&ipa->endpoint[endpoint_id]); + } +} + +static int ipa_endpoint_init_one(struct ipa *ipa, + const struct gsi_ipa_endpoint_data *data) +{ + struct ipa_endpoint *endpoint; + + if (data->endpoint_id >= IPA_ENDPOINT_MAX) + return -EIO; + endpoint = &ipa->endpoint[data->endpoint_id]; + + if (data->ee_id == GSI_EE_AP) + ipa->endpoint_map[data->channel_id] = endpoint; + + endpoint->ipa = ipa; + endpoint->ee_id = data->ee_id; + endpoint->channel_id = data->channel_id; + endpoint->endpoint_id = data->endpoint_id; + endpoint->toward_ipa = data->toward_ipa; + endpoint->data = &data->endpoint; + + if (endpoint->data->support_flt) + ipa->filter_support |= BIT(endpoint->endpoint_id); + + ipa->initialized |= BIT(endpoint->endpoint_id); + + return 0; +} + +void ipa_endpoint_exit_one(struct ipa_endpoint *endpoint) +{ + endpoint->ipa->initialized &= ~BIT(endpoint->endpoint_id); +} + +int ipa_endpoint_init(struct ipa *ipa, u32 data_count, + const struct gsi_ipa_endpoint_data *data) +{ + u32 initialized; + int ret; + u32 i; + + ipa->initialized = 0; + + ipa->filter_support = 0; + for (i = 0; i < data_count; i++) { + ret = ipa_endpoint_init_one(ipa, &data[i]); + if (ret) + goto err_endpoint_unwind; + } + dev_dbg(&ipa->pdev->dev, "initialized 0x%08x\n", ipa->initialized); + + /* Verify the bitmap of endpoints that support filtering. */ + dev_dbg(&ipa->pdev->dev, "filter_support 0x%08x\n", + ipa->filter_support); + if (!ipa->filter_support) + goto err_endpoint_unwind; + if (hweight32(ipa->filter_support) > IPA_SMEM_FLT_COUNT) + goto err_endpoint_unwind; + + return 0; + +err_endpoint_unwind: + initialized = ipa->initialized; + while (initialized) { + enum ipa_endpoint_id endpoint_id = __fls(initialized); + + initialized ^= BIT(endpoint_id); + + ipa_endpoint_exit_one(&ipa->endpoint[endpoint_id]); + } + + return ret; +} + +void ipa_endpoint_exit(struct ipa *ipa) +{ + u32 initialized = ipa->initialized; + + while (initialized) { + enum ipa_endpoint_id endpoint_id = __fls(initialized); + + initialized ^= BIT(endpoint_id); + + ipa_endpoint_exit_one(&ipa->endpoint[endpoint_id]); + } +} diff --git a/drivers/net/ipa/ipa_endpoint.h b/drivers/net/ipa/ipa_endpoint.h new file mode 100644 index 000000000000..c9f7ccc59a5a --- /dev/null +++ b/drivers/net/ipa/ipa_endpoint.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2019 Linaro Ltd. + */ +#ifndef _IPA_ENDPOINT_H_ +#define _IPA_ENDPOINT_H_ + +#include +#include +#include + +#include "gsi.h" +#include "ipa_reg.h" + +struct net_device; +struct sk_buff; + +struct ipa; +struct gsi_ipa_endpoint_data; + +#define IPA_MTU ETH_DATA_LEN + +enum ipa_endpoint_id { + IPA_ENDPOINT_INVALID = 0, + IPA_ENDPOINT_AP_MODEM_TX = 2, + IPA_ENDPOINT_MODEM_LAN_TX = 3, + IPA_ENDPOINT_MODEM_COMMAND_TX = 4, + IPA_ENDPOINT_AP_COMMAND_TX = 5, + IPA_ENDPOINT_MODEM_AP_TX = 6, + IPA_ENDPOINT_AP_LAN_RX = 9, + IPA_ENDPOINT_AP_MODEM_RX = 10, + IPA_ENDPOINT_MODEM_AP_RX = 12, + IPA_ENDPOINT_MODEM_LAN_RX = 13, +}; + +#define IPA_ENDPOINT_MAX 32 /* Max supported */ + +/** + * struct ipa_endpoint - IPA endpoint information + * @client: Client associated with the endpoint + * @channel_id: EP's GSI channel + * @evt_ring_id: EP's GSI channel event ring + */ +struct ipa_endpoint { + struct ipa *ipa; + enum ipa_seq_type seq_type; + enum gsi_ee_id ee_id; + u32 channel_id; + enum ipa_endpoint_id endpoint_id; + u32 toward_ipa; /* Boolean */ + const struct ipa_endpoint_data *data; + + u32 trans_tre_max; /* maximum descriptors per transaction */ + u32 evt_ring_id; + + /* Net device this endpoint is associated with, if any */ + struct net_device *netdev; + + /* Receive buffer replenishing for RX endpoints */ + u32 replenish_enabled; /* Boolean */ + u32 replenish_ready; + atomic_t replenish_saved; + atomic_t replenish_backlog; + struct delayed_work replenish_work; /* global wq */ +}; + +bool ipa_endpoint_init_ctrl(struct ipa_endpoint *endpoint, bool suspend_delay); + +int ipa_endpoint_skb_tx(struct ipa_endpoint *endpoint, struct sk_buff *skb); + +int ipa_endpoint_stop(struct ipa_endpoint *endpoint); + +void ipa_endpoint_exit_one(struct ipa_endpoint *endpoint); + +bool ipa_endpoint_enabled(struct ipa_endpoint *endpoint); +int ipa_endpoint_enable_one(struct ipa_endpoint *endpoint); +void ipa_endpoint_disable_one(struct ipa_endpoint *endpoint); + +void ipa_endpoint_default_route_setup(struct ipa_endpoint *endpoint); +void ipa_endpoint_default_route_teardown(struct ipa_endpoint *endpoint); + +void ipa_endpoint_suspend(struct ipa_endpoint *endpoint); +void ipa_endpoint_resume(struct ipa_endpoint *endpoint); + +void ipa_endpoint_setup(struct ipa *ipa); +void ipa_endpoint_teardown(struct ipa *ipa); + +int ipa_endpoint_init(struct ipa *ipa, u32 data_count, + const struct gsi_ipa_endpoint_data *data); +void ipa_endpoint_exit(struct ipa *ipa); + +void ipa_endpoint_skb_tx_complete(struct gsi_trans *trans); +void ipa_endpoint_rx_complete(struct gsi_trans *trans); + + +#endif /* _IPA_ENDPOINT_H_ */ From patchwork Fri May 31 03:53:43 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Elder X-Patchwork-Id: 165500 Delivered-To: patch@linaro.org Received: by 2002:a92:9e1a:0:0:0:0:0 with SMTP id q26csp186678ili; Thu, 30 May 2019 20:54:53 -0700 (PDT) X-Google-Smtp-Source: APXvYqx7w7yZ/a2M8GznZy/GxMQ3Oc25Qhf05kJUCf8E6nDL23CIlv7uKVeQ0e56EQVFGVvagtyA X-Received: by 2002:a62:5bc1:: with SMTP id p184mr7199726pfb.154.1559274893478; Thu, 30 May 2019 20:54:53 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1559274893; cv=none; d=google.com; s=arc-20160816; b=eZ8NqatrEGuVmZe49G42PwxdT/3RZJMFXEm1sSHzh34b2EB2pWLwYkGelXnmtPlBVR BrV4IM76z8XncZmeJpYhVZu61esonqW5jyzgDJCqYVq+U0lh9g0NWe6viX/hLW4WzZLp XBT72ikYo6eqSsgsFJd8RKcCydVuwncvDcoHvvp1H+Q8xzi8KFRHFEHyqU0JrTtU6lL6 DvcKRPMyQdr+H7UVvcZtQU6J+y1+T3kzxGWp4JGA1zs96/8UW4gWQNVt2C4XKX7K0dSa 4eZlFclpyQqxKv2bFElMeMlqyOfN8rdwTy3cl7n0qZF9hXDF58TPl+dtKLZubynJLERv Z7YA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=aKNmhoCuw/eCc7wd53pD7Lrwug+Yfz2FeaJH4e2wTqA=; b=gz7tG48FIwyK3LeQYS5bpOfjcqt/4BJIaSKPifxniCcOO+OB8cii1jiaXc9x0XX0Wz cS8tnVKkz3e4vMMPLv8+PCpzzd3N4EH1W2KBSZl5OgJvOldNG8TXbsKG8/CIICWlNk+0 jL7PIrw9zLhke7KTfgyhbkrZVMyk6YRgCgXV+TpE8xm9rfTPuVVMGvggfGQg9e58TJI2 NTxHd8gRljXsfp6KZQx8M7p0hziXuDL8S1Oe8coBGiTMvUdzLPuEaiKsMLtmfC7RVmH2 oVzPmYeD1gWdRBH3EZv2j5RajLG16j+O7YwNxw0B5DqJ6OJ8QICm8wuFfi+Zvr4lyU+n bxNg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=tDmMop7p; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id f39si4698821plb.234.2019.05.30.20.54.53; Thu, 30 May 2019 20:54:53 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=tDmMop7p; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726973AbfEaDyw (ORCPT + 7 others); Thu, 30 May 2019 23:54:52 -0400 Received: from mail-it1-f195.google.com ([209.85.166.195]:32781 "EHLO mail-it1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726946AbfEaDyN (ORCPT ); Thu, 30 May 2019 23:54:13 -0400 Received: by mail-it1-f195.google.com with SMTP id j17so9844476itk.0 for ; Thu, 30 May 2019 20:54:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=aKNmhoCuw/eCc7wd53pD7Lrwug+Yfz2FeaJH4e2wTqA=; b=tDmMop7pS/Jx2ovpSkXLmS4AZMBK4I02veODFx6Oic0oGU/s5Gg6/sXkjoVn0icAns sEHHieQF3cTNrmm8f/nKb/X5cIL3RmqZjMZeYCrAUxeprVgKeRjjkog0rV/lCMWj5Vbu EPNj0WE3ZbAhA9e0pIWQ5FUwb69ayx+SU7Z6/OBnhY4a+umf+W7E+gFOp45E1dqXBCzW MVXEcxZHtYo+KdPV9qye+ZOTTNLD572iR5m3jCzkt55XZqU5h4fuGovqEJ4LeWFNHC/y Ch86qcwExxwZvIGs1K7pB9sygEm6WRZClhZyMWcH/t8AIkNN4/qV9I4CydGBrNKktH6H j1CA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=aKNmhoCuw/eCc7wd53pD7Lrwug+Yfz2FeaJH4e2wTqA=; b=RgujntpoHdnwllEXY3WIgXQLYRLfpTukFyA3ewwjBlTZCLX1yIEqie20HekJfavzSA eT/u50Xzj3bArmDzvLstDJZooer0qSIS87yOcTIitu3XRevdeGt2SACJjp1ERktGnrDv 02fgrLbxfXYqLLeInPY386uyuvhT0UvZ8VGbEX3jV2kO47sECF4cVwazSF5n72bYKU8k bG9OpOAimt30YMrqpwYtMlUGelKiMKGATPMDX3JC2e/pdDtJIOZD0ZkXxa+ga8vytgb7 CsY6pDxCShqmFYJcYILLVOgsTz6caQ0/4YT1lsWDm56FWOod8Rwl/Budir2/HfHzb3IM 3yMg== X-Gm-Message-State: APjAAAWgRSzTwFHdKcyaJzbXwOjfAcPAdYwuUNS0yujPNZzE8vMtjzYq +5Y9iQqCWRjhcMg9YurCImrFLA== X-Received: by 2002:a02:950a:: with SMTP id y10mr5357447jah.26.1559274851907; Thu, 30 May 2019 20:54:11 -0700 (PDT) Received: from localhost.localdomain (c-71-195-29-92.hsd1.mn.comcast.net. [71.195.29.92]) by smtp.gmail.com with ESMTPSA id q15sm1626947ioi.15.2019.05.30.20.54.10 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 30 May 2019 20:54:11 -0700 (PDT) From: Alex Elder To: davem@davemloft.net, arnd@arndb.de, bjorn.andersson@linaro.org, ilias.apalodimas@linaro.org Cc: evgreen@chromium.org, benchan@google.com, ejcaruso@google.com, cpratapa@codeaurora.org, syadagir@codeaurora.org, subashab@codeaurora.org, abhishek.esse@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-soc@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org Subject: [PATCH v2 12/17] soc: qcom: ipa: IPA network device and microcontroller Date: Thu, 30 May 2019 22:53:43 -0500 Message-Id: <20190531035348.7194-13-elder@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190531035348.7194-1-elder@linaro.org> References: <20190531035348.7194-1-elder@linaro.org> MIME-Version: 1.0 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org This patch includes the code that implements a Linux network device, using one TX and one RX IPA endpoint. It is used to implement the network device representing the modem and its connection to wireless networks. There are only a few things that are really modem-specific though, and they aren't clearly called out here. Such distinctions will be made clearer if we wish to support a network device for anything other than the modem. Sort of unrelated, this patch also includes the code supporting the microcontroller CPU present on the IPA. The microcontroller can be used to implement special handling of packets, but at this time we don't support that. Still, it is a component that needs to be initialized, and in the event of a crash we need to do some synchronization between the AP and the microcontroller. Signed-off-by: Alex Elder --- drivers/net/ipa/ipa_netdev.c | 251 +++++++++++++++++++++++++++++++++++ drivers/net/ipa/ipa_netdev.h | 24 ++++ drivers/net/ipa/ipa_uc.c | 208 +++++++++++++++++++++++++++++ drivers/net/ipa/ipa_uc.h | 32 +++++ 4 files changed, 515 insertions(+) create mode 100644 drivers/net/ipa/ipa_netdev.c create mode 100644 drivers/net/ipa/ipa_netdev.h create mode 100644 drivers/net/ipa/ipa_uc.c create mode 100644 drivers/net/ipa/ipa_uc.h -- 2.20.1 diff --git a/drivers/net/ipa/ipa_netdev.c b/drivers/net/ipa/ipa_netdev.c new file mode 100644 index 000000000000..19c73c4da02b --- /dev/null +++ b/drivers/net/ipa/ipa_netdev.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018-2019 Linaro Ltd. + */ + +/* Modem Transport Network Driver. */ + +#include +#include +#include +#include +#include + +#include "ipa.h" +#include "ipa_data.h" +#include "ipa_endpoint.h" +#include "ipa_mem.h" +#include "ipa_netdev.h" +#include "ipa_qmi.h" + +#define IPA_NETDEV_NAME "rmnet_ipa%d" + +#define TAILROOM 0 /* for padding by mux layer */ + +#define IPA_NETDEV_TIMEOUT 10 /* seconds */ + +/** struct ipa_priv - IPA network device private data */ +struct ipa_priv { + struct ipa_endpoint *tx_endpoint; + struct ipa_endpoint *rx_endpoint; +}; + +/** ipa_netdev_open() - Opens the modem network interface */ +static int ipa_netdev_open(struct net_device *netdev) +{ + struct ipa_priv *priv = netdev_priv(netdev); + int ret; + + ret = ipa_endpoint_enable_one(priv->tx_endpoint); + if (ret) + return ret; + ret = ipa_endpoint_enable_one(priv->rx_endpoint); + if (ret) + goto err_disable_tx; + + netif_start_queue(netdev); + + return 0; + +err_disable_tx: + ipa_endpoint_disable_one(priv->tx_endpoint); + + return ret; +} + +/** ipa_netdev_stop() - Stops the modem network interface. */ +static int ipa_netdev_stop(struct net_device *netdev) +{ + struct ipa_priv *priv = netdev_priv(netdev); + + netif_stop_queue(netdev); + + ipa_endpoint_disable_one(priv->rx_endpoint); + ipa_endpoint_disable_one(priv->tx_endpoint); + + return 0; +} + +/** ipa_netdev_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_netdev_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct net_device_stats *stats = &netdev->stats; + struct ipa_priv *priv = netdev_priv(netdev); + struct ipa_endpoint *endpoint; + u32 skb_len = skb->len; + + if (!skb_len) + goto err_drop; + + endpoint = priv->tx_endpoint; + if (endpoint->data->config.qmap && skb->protocol != htons(ETH_P_MAP)) + goto err_drop; + + if (ipa_endpoint_skb_tx(endpoint, skb)) + return NETDEV_TX_BUSY; + + stats->tx_packets++; + stats->tx_bytes += skb_len; + + return NETDEV_TX_OK; + +err_drop: + dev_kfree_skb_any(skb); + stats->tx_dropped++; + + return NETDEV_TX_OK; +} + +void ipa_netdev_skb_rx(struct net_device *netdev, struct sk_buff *skb) +{ + struct net_device_stats *stats = &netdev->stats; + + if (skb) { + skb->dev = netdev; + skb->protocol = htons(ETH_P_MAP); + stats->rx_packets++; + stats->rx_bytes += skb->len; + + (void)netif_receive_skb(skb); + } else { + stats->rx_dropped++; + } +} + +static const struct net_device_ops ipa_netdev_ops = { + .ndo_open = ipa_netdev_open, + .ndo_stop = ipa_netdev_stop, + .ndo_start_xmit = ipa_netdev_xmit, +}; + +/** netdev_setup() - netdev setup function */ +static void netdev_setup(struct net_device *netdev) +{ + netdev->netdev_ops = &ipa_netdev_ops; + ether_setup(netdev); + /* No header ops (override value set by ether_setup()) */ + netdev->header_ops = NULL; + netdev->type = ARPHRD_RAWIP; + netdev->hard_header_len = 0; + netdev->max_mtu = IPA_MTU; + netdev->mtu = netdev->max_mtu; + netdev->addr_len = 0; + netdev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + /* The endpoint is configured for QMAP */ + netdev->needed_headroom = sizeof(struct rmnet_map_header); + netdev->needed_tailroom = TAILROOM; + netdev->watchdog_timeo = IPA_NETDEV_TIMEOUT * HZ; + netdev->hw_features = NETIF_F_SG; +} + +/** ipa_netdev_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. + */ +void ipa_netdev_suspend(struct net_device *netdev) +{ + struct ipa_priv *priv = netdev_priv(netdev); + + netif_stop_queue(netdev); + + ipa_endpoint_suspend(priv->tx_endpoint); + ipa_endpoint_suspend(priv->rx_endpoint); +} + +/** ipa_netdev_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. + */ +void ipa_netdev_resume(struct net_device *netdev) +{ + struct ipa_priv *priv = netdev_priv(netdev); + + ipa_endpoint_resume(priv->rx_endpoint); + ipa_endpoint_resume(priv->tx_endpoint); + + netif_wake_queue(netdev); +} + +struct net_device *ipa_netdev_setup(struct ipa *ipa, + struct ipa_endpoint *rx_endpoint, + struct ipa_endpoint *tx_endpoint) +{ + struct net_device *netdev; + struct ipa_priv *priv; + int ret; + + /* Zero modem shared memory before we begin */ + ret = ipa_smem_zero_modem(ipa); + if (ret) + return ERR_PTR(ret); + + /* Start QMI communication with the modem */ + ret = ipa_qmi_setup(ipa); + if (ret) + return ERR_PTR(ret); + + netdev = alloc_netdev(sizeof(struct ipa_priv), IPA_NETDEV_NAME, + NET_NAME_UNKNOWN, netdev_setup); + if (!netdev) { + ret = -ENOMEM; + goto err_qmi_exit; + } + + rx_endpoint->netdev = netdev; + tx_endpoint->netdev = netdev; + + priv = netdev_priv(netdev); + priv->tx_endpoint = tx_endpoint; + priv->rx_endpoint = rx_endpoint; + + ret = register_netdev(netdev); + if (ret) + goto err_free_netdev; + + return netdev; + +err_free_netdev: + free_netdev(netdev); +err_qmi_exit: + ipa_qmi_teardown(ipa); + + return ERR_PTR(ret); +} + +void ipa_netdev_teardown(struct net_device *netdev) +{ + struct ipa_priv *priv = netdev_priv(netdev); + struct ipa *ipa = priv->tx_endpoint->ipa; + + if (!netif_queue_stopped(netdev)) + (void)ipa_netdev_stop(netdev); + + unregister_netdev(netdev); + + free_netdev(netdev); + + ipa_qmi_teardown(ipa); +} diff --git a/drivers/net/ipa/ipa_netdev.h b/drivers/net/ipa/ipa_netdev.h new file mode 100644 index 000000000000..8ab1e8ea0b4a --- /dev/null +++ b/drivers/net/ipa/ipa_netdev.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018-2019 Linaro Ltd. + */ +#ifndef _IPA_NETDEV_H_ +#define _IPA_NETDEV_H_ + +struct ipa; +struct ipa_endpoint; +struct net_device; +struct sk_buff; + +struct net_device *ipa_netdev_setup(struct ipa *ipa, + struct ipa_endpoint *rx_endpoint, + struct ipa_endpoint *tx_endpoint); +void ipa_netdev_teardown(struct net_device *netdev); + +void ipa_netdev_skb_rx(struct net_device *netdev, struct sk_buff *skb); + +void ipa_netdev_suspend(struct net_device *netdev); +void ipa_netdev_resume(struct net_device *netdev); + +#endif /* _IPA_NETDEV_H_ */ diff --git a/drivers/net/ipa/ipa_uc.c b/drivers/net/ipa/ipa_uc.c new file mode 100644 index 000000000000..57256d1c3b90 --- /dev/null +++ b/drivers/net/ipa/ipa_uc.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018-2019 Linaro Ltd. + */ + +#include +#include +#include + +#include "ipa.h" +#include "ipa_clock.h" +#include "ipa_uc.h" + +/** + * DOC: The IPA embedded microcontroller + * + * The IPA incorporates a microcontroller that is able to do some additional + * handling/offloading of network activity. The current code makes + * essentially no use of the microcontroller, but it still requires some + * initialization. It needs to be notified in the event the AP crashes. + * + * The microcontroller can generate two interrupts to the AP. One interrupt + * is used to indicate that a response to a request from the AP is available. + * The other is used to notify the AP of the occurrence of an event. In + * addition, the AP can interrupt the microcontroller by writing a register. + * + * A 128 byte block of structured memory within the IPA SRAM is used together + * with these interrupts to implement the communication interface between the + * AP and the IPA microcontroller. Each side writes data to the shared area + * before interrupting its peer, which will read the written data in response + * to the interrupt. Some information found in the shared area is currently + * unused. All remaining space in the shared area is reserved, and must not + * be read or written by the AP. + */ +/* Supports hardware interface version 0x2000 */ + +/* Offset relative to the base of the IPA shared address space of the + * shared region used for communication with the microcontroller. The + * region is 128 bytes in size, but only the first 40 bytes are used. + */ +#define IPA_SMEM_UC_OFFSET 0x0000 + +/* Delay to allow a the microcontroller to save state when crashing */ +#define IPA_SEND_DELAY 100 /* microseconds */ + +/** + * 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 { + u8 command; /* enum ipa_uc_command */ + u8 reserved0[3]; + __le32 command_param; + __le32 command_param_hi; + u8 response; /* enum ipa_uc_response */ + u8 reserved1[3]; + __le32 response_param; + u8 event; /* enum ipa_uc_event */ + u8 reserved2[3]; + + __le32 event_param; + __le32 first_error_address; + u8 hw_state; + u8 warning_counter; + __le16 reserved3; + __le16 interface_version; + __le16 reserved4; +}; + +/** enum ipa_uc_command - commands from the AP to the microcontroller */ +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 - microcontroller response codes */ +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, +}; + +/** enum ipa_uc_event - common cpu events reported by the microcontroller */ +enum ipa_uc_event { + IPA_UC_EVENT_NO_OP = 0, + IPA_UC_EVENT_ERROR = 1, + IPA_UC_EVENT_LOG_INFO = 2, +}; + +/* Microcontroller event IPA interrupt handler */ +static void ipa_uc_event_handler(struct ipa *ipa, + enum ipa_interrupt_id interrupt_id) +{ + struct ipa_uc_shared_area *shared; + + shared = ipa->shared_virt + IPA_SMEM_UC_OFFSET; + dev_err(&ipa->pdev->dev, "unsupported microcontroller event %hhu\n", + shared->event); + WARN_ON(shared->event == IPA_UC_EVENT_ERROR); +} + +/* Microcontroller response IPA interrupt handler */ +static void ipa_uc_response_hdlr(struct ipa *ipa, + enum ipa_interrupt_id interrupt_id) +{ + struct ipa_uc_shared_area *shared; + + /* 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. + * + * We can drop the clock reference taken in ipa_uc_init() once we + * know the microcontroller has finished its initialization. + */ + shared = ipa->shared_virt + IPA_SMEM_UC_OFFSET; + switch (shared->response) { + case IPA_UC_RESPONSE_INIT_COMPLETED: + ipa->uc_loaded = 1; + ipa_clock_put(ipa->clock); + break; + default: + dev_warn(&ipa->pdev->dev, + "unsupported microcontroller response %hhu\n", + shared->response); + break; + } +} + +/* ipa_uc_setup() - Set up the microcontroller */ +void ipa_uc_setup(struct ipa *ipa) +{ + /* The microcontroller needs the IPA clock running until it has + * completed its initialization. It signals this by sending an + * INIT_COMPLETED response message to the AP. This could occur after + * we have finished doing the rest of the IPA initialization, so we + * need to take an extra "proxy" reference, and hold it until we've + * received that signal. (This reference is dropped in + * ipa_uc_response_hdlr(), above.) + */ + ipa_clock_get(ipa->clock); + + ipa->uc_loaded = 0; + ipa_interrupt_add(ipa->interrupt, IPA_INTERRUPT_UC_0, + ipa_uc_event_handler); + ipa_interrupt_add(ipa->interrupt, IPA_INTERRUPT_UC_1, + ipa_uc_response_hdlr); +} + +/* Inverse of ipa_uc_setup() */ +void ipa_uc_teardown(struct ipa *ipa) +{ + ipa_interrupt_remove(ipa->interrupt, IPA_INTERRUPT_UC_1); + ipa_interrupt_remove(ipa->interrupt, IPA_INTERRUPT_UC_0); + if (!ipa->uc_loaded) + ipa_clock_put(ipa->clock); +} + +/* Send a command to the microcontroller */ +static void send_uc_command(struct ipa *ipa, u32 command, u32 command_param) +{ + struct ipa_uc_shared_area *shared; + + shared = ipa->shared_virt + IPA_SMEM_UC_OFFSET; + shared->command = command; + shared->command_param = cpu_to_le32(command_param); + shared->command_param_hi = 0; + shared->response = 0; + shared->response_param = 0; + + iowrite32(1, ipa->reg_virt + IPA_REG_IRQ_UC_OFFSET); +} + +/* Tell the microcontroller the AP is shutting down */ +void ipa_uc_panic_notifier(struct ipa *ipa) +{ + if (!ipa->uc_loaded) + return; + + send_uc_command(ipa, IPA_UC_COMMAND_ERR_FATAL, 0); + + /* give uc enough time to save state */ + udelay(IPA_SEND_DELAY); +} diff --git a/drivers/net/ipa/ipa_uc.h b/drivers/net/ipa/ipa_uc.h new file mode 100644 index 000000000000..c258cb6e1161 --- /dev/null +++ b/drivers/net/ipa/ipa_uc.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2019 Linaro Ltd. + */ +#ifndef _IPA_UC_H_ +#define _IPA_UC_H_ + +struct ipa; + +/** + * ipa_uc_setup() - set up the IPA microcontroller subsystem + * @ipa: IPA pointer + */ +void ipa_uc_setup(struct ipa *ipa); + +/** + * ipa_uc_teardown() - inverse of ipa_uc_setup() + * @ipa: IPA pointer + */ +void ipa_uc_teardown(struct ipa *ipa); + +/** + * ipa_uc_panic_notifier() + * @ipa: IPA pointer + * + * Notifier function called when the system crashes, to inform the + * microcontroller of the event. + */ +void ipa_uc_panic_notifier(struct ipa *ipa); + +#endif /* _IPA_UC_H_ */ From patchwork Fri May 31 03:53:45 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Elder X-Patchwork-Id: 165494 Delivered-To: patch@linaro.org Received: by 2002:a92:9e1a:0:0:0:0:0 with SMTP id q26csp186034ili; Thu, 30 May 2019 20:54:17 -0700 (PDT) X-Google-Smtp-Source: APXvYqwpyux4MXuAnDAwz9h4A6m953T16ltXP/zDlhUpDZLJFFO6LMTQVI8UVWwkZWwEJC3OdlT8 X-Received: by 2002:a63:8449:: with SMTP id k70mr6987931pgd.208.1559274857242; Thu, 30 May 2019 20:54:17 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1559274857; cv=none; d=google.com; s=arc-20160816; b=jsR85xJyt+BTolVyqzW4BhHcKpctHC6fupCamy9km5bTzOxFIiM6Y6UaVtlRFOFJf+ BfqI3Bz8zOe52Tp7m4B4cBOOkH5UDbTIXB4KZ5vv40D1Pqnd0bLqcE2yDtOQRnpskXSU BD6fuZ9EUsi0MmQpPfT+s8Kzvv8zVyOwFgsExe/yg4hYKfipBNXwLNaAGmjr/kTM8n6A TUP6+DWyhQSxpbGOoow+ccodOAuXJ5S46LZGYakNcQtuLtHhu/0GJZfjxGydjLXMZEbp D0lmKT5ikzi0Do1K/Jhj0LFvRmppkMPlyJbi85Xg6jHxePQs1z/uEQinPhSgA/J2pN/B mvGQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=hqFf2+cwBc4XPU/wNoeNiXzq2lmzHpPCTPDaeLdQu4s=; b=DYzXlvwmVoc3StO0LOQyeLWZBYP5XWn9hL0bLsV5q7O43kpb7teN7aw01Bm97YBHJb 9DRtfa5pfok9pBAXL4ujd1R1y6cYuB7pqQejtxUvU6Yxb39te+FQ2FozdB7HjZnwlkpN RYtS971lMKzeT7cJHxL9oDkH75bZvu4WP7takJ/H7ibsm4y5Hi58XpPP2WIxF29U2oSx J24RRfkf3mrGPLS6ph90ftr3JeoP1I8U2PeUKIbAsyxH6Y8464pWOHcb350mOx0CMbZV KdBrbYNagfESZIliKjMPHBfMZxTxK8AcRsAuS8HSa4a1RiGogEyIz65Rc5zl/w/miL+s u27A== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=dwV6lcEW; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id m21si4637715pgl.45.2019.05.30.20.54.16; Thu, 30 May 2019 20:54:17 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=dwV6lcEW; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726991AbfEaDyQ (ORCPT + 7 others); Thu, 30 May 2019 23:54:16 -0400 Received: from mail-it1-f194.google.com ([209.85.166.194]:40103 "EHLO mail-it1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726980AbfEaDyP (ORCPT ); Thu, 30 May 2019 23:54:15 -0400 Received: by mail-it1-f194.google.com with SMTP id h11so13032005itf.5 for ; Thu, 30 May 2019 20:54:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=hqFf2+cwBc4XPU/wNoeNiXzq2lmzHpPCTPDaeLdQu4s=; b=dwV6lcEWrxc5B6DJHcpq3zyz85EXyb7kFr66i80SndfVrNSU4O6J2BhCuq4xE9YuzG QJEmlxKobURV1wOZvw/c6rRePWj08be/9fs9y2H4V4dAqvoWGBzKvPWGy3M/GlkBE0dG fB0+1S4FXZaI2IcfQfZRZVtRvdl6d9HBSKFx7wjh0vvlKTQbnW0gnMSsdPc4JZbOEZxZ 9+ldXNVSP69hgIFuV09GrO8sJw5L3I7P9UL2lk12RFXNAOLaRkF4zItXR5B1Uy4SSQ5S 2lnj0k2mR9SfKdIkG9tTaENn3hV+GHxRfykjUcndTjGzcKr5Fj37FZl1j9/iwIRDGMNH /bvQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=hqFf2+cwBc4XPU/wNoeNiXzq2lmzHpPCTPDaeLdQu4s=; b=attpYA1YbOLUMyQkaaB65QLvxpQBussKhThnIDWsS7DtOX9L0etbDqqCJlvaSickff 6mkU/MyZrKCAqD1xyUxr03XclenRtVQtxgfxrGZ1Y8/CWN0xmCvQo9VzvWu+ohC781Ks +HQbqJF8ztOTF9B+JjSNpcUxNvAsj7lypOXG+SGUnskjtRaLdAhVxpJ6izt0g4Jvk3pa IK8AQyL+gsxYlDI79xCuD1AWLq2BIGYVf04NhZ6tGXBIVz4lnB2ZLZwCCeAFvzsTxJP0 IYflZQl/mXeFlccw5CWTh15QVo3fVx7Wdob8fbo2ulT6A/HskxddvhLm6bjtCAVB3mnP iAtg== X-Gm-Message-State: APjAAAVyzcl1QqJSNDRZ9i0npnsv+i3d90MwI0IGj3BBZSFjqQxNsIvi HCUmBTia2ri4IMRoaxUKbeudzw== X-Received: by 2002:a24:a09:: with SMTP id 9mr5343497itw.146.1559274854371; Thu, 30 May 2019 20:54:14 -0700 (PDT) Received: from localhost.localdomain (c-71-195-29-92.hsd1.mn.comcast.net. [71.195.29.92]) by smtp.gmail.com with ESMTPSA id q15sm1626947ioi.15.2019.05.30.20.54.13 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 30 May 2019 20:54:13 -0700 (PDT) From: Alex Elder To: davem@davemloft.net, arnd@arndb.de, bjorn.andersson@linaro.org, ilias.apalodimas@linaro.org Cc: evgreen@chromium.org, benchan@google.com, ejcaruso@google.com, cpratapa@codeaurora.org, syadagir@codeaurora.org, subashab@codeaurora.org, abhishek.esse@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-soc@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org Subject: [PATCH v2 14/17] soc: qcom: ipa: support build of IPA code Date: Thu, 30 May 2019 22:53:45 -0500 Message-Id: <20190531035348.7194-15-elder@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190531035348.7194-1-elder@linaro.org> References: <20190531035348.7194-1-elder@linaro.org> MIME-Version: 1.0 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add build and Kconfig support for the Qualcomm IPA driver. Signed-off-by: Alex Elder --- drivers/net/Kconfig | 2 ++ drivers/net/Makefile | 1 + drivers/net/ipa/Kconfig | 16 ++++++++++++++++ drivers/net/ipa/Makefile | 7 +++++++ 4 files changed, 26 insertions(+) create mode 100644 drivers/net/ipa/Kconfig create mode 100644 drivers/net/ipa/Makefile -- 2.20.1 diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 48e209e55843..d87fe174eb9f 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -388,6 +388,8 @@ source "drivers/net/fddi/Kconfig" source "drivers/net/hippi/Kconfig" +source "drivers/net/ipa/Kconfig" + config NET_SB1000 tristate "General Instruments Surfboard 1000" depends on PNP diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 0d3ba056cda3..ff8918fe09b0 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_ETHERNET) += ethernet/ obj-$(CONFIG_FDDI) += fddi/ obj-$(CONFIG_HIPPI) += hippi/ obj-$(CONFIG_HAMRADIO) += hamradio/ +obj-$(CONFIG_IPA) += ipa/ obj-$(CONFIG_PLIP) += plip/ obj-$(CONFIG_PPP) += ppp/ obj-$(CONFIG_PPP_ASYNC) += ppp/ diff --git a/drivers/net/ipa/Kconfig b/drivers/net/ipa/Kconfig new file mode 100644 index 000000000000..b1e3f7405992 --- /dev/null +++ b/drivers/net/ipa/Kconfig @@ -0,0 +1,16 @@ +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. diff --git a/drivers/net/ipa/Makefile b/drivers/net/ipa/Makefile new file mode 100644 index 000000000000..a43039c09a25 --- /dev/null +++ b/drivers/net/ipa/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_IPA) += ipa.o + +ipa-y := ipa_main.o ipa_clock.o ipa_mem.o \ + ipa_interrupt.o gsi.o gsi_trans.o \ + ipa_gsi.o ipa_smp2p.o ipa_uc.o \ + ipa_endpoint.o ipa_cmd.o ipa_netdev.o \ + ipa_qmi.o ipa_qmi_msg.o ipa_data-sdm845.o