From patchwork Thu May 20 14:01:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443858 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 51753C433ED for ; Thu, 20 May 2021 14:05:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3251C6109F for ; Thu, 20 May 2021 14:05:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234655AbhETOGd (ORCPT ); Thu, 20 May 2021 10:06:33 -0400 Received: from mga11.intel.com ([192.55.52.93]:5246 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235527AbhETOFd (ORCPT ); Thu, 20 May 2021 10:05:33 -0400 IronPort-SDR: HZ2IxHkJ5LLqbeiIVZXBpl2qDvco3z/Te2hJYRb5AjgfSiLl6HfvRt50OePkTFJQGB2fA0Nhn7 9D3nLHHwZx3g== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144481" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144481" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:02:25 -0700 IronPort-SDR: c4x33oM0Wf0UVmilDXgx8zl+hDCsHwAME2oLDg7HQnSOccG9XCoEJnssHINic7Aobmq09/l+iQ nz6/iYAG6+eA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407423" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:02:22 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 01/16] net: iosm: entry point Date: Thu, 20 May 2021 19:31:43 +0530 Message-Id: <20210520140158.10132-2-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 1) Register IOSM driver with kernel to manage Intel WWAN PCIe device(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7560_ID). 2) Exposes the EP PCIe device capability to Host PCIe core. 3) Initializes PCIe EP configuration and defines PCIe driver probe, remove and power management OPS. 4) Allocate and map(dma) skb memory for data communication from device to kernel and vice versa. Signed-off-by: M Chetan Kumar --- V3: * Removed Module Author define. * Aligned ipc_ prefix for function name to be consistent across file. v2: * Implement module_init() & exit() callbacks for rtnl_link. * Documentation correction for function signature. * Fix coverity warnings. --- drivers/net/wwan/iosm/iosm_ipc_pcie.c | 586 ++++++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_pcie.h | 211 ++++++++++ 2 files changed, 797 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_pcie.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_pcie.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_pcie.c b/drivers/net/wwan/iosm/iosm_ipc_pcie.c new file mode 100644 index 000000000000..0c26047ebc1c --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_pcie.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include +#include +#include +#include + +#include "iosm_ipc_imem.h" +#include "iosm_ipc_pcie.h" +#include "iosm_ipc_protocol.h" + +MODULE_DESCRIPTION("IOSM Driver"); +MODULE_LICENSE("GPL v2"); + +/* WWAN GUID */ +static guid_t wwan_acpi_guid = GUID_INIT(0xbad01b75, 0x22a8, 0x4f48, 0x87, 0x92, + 0xbd, 0xde, 0x94, 0x67, 0x74, 0x7d); + +static void ipc_pcie_resources_release(struct iosm_pcie *ipc_pcie) +{ + /* Free the MSI resources. */ + ipc_release_irq(ipc_pcie); + + /* Free mapped doorbell scratchpad bus memory into CPU space. */ + iounmap(ipc_pcie->scratchpad); + + /* Free mapped IPC_REGS bus memory into CPU space. */ + iounmap(ipc_pcie->ipc_regs); + + /* Releases all PCI I/O and memory resources previously reserved by a + * successful call to pci_request_regions. Call this function only + * after all use of the PCI regions has ceased. + */ + pci_release_regions(ipc_pcie->pci); +} + +static void ipc_pcie_cleanup(struct iosm_pcie *ipc_pcie) +{ + /* Free the shared memory resources. */ + ipc_imem_cleanup(ipc_pcie->imem); + + ipc_pcie_resources_release(ipc_pcie); + + /* Signal to the system that the PCI device is not in use. */ + pci_disable_device(ipc_pcie->pci); +} + +static void ipc_pcie_deinit(struct iosm_pcie *ipc_pcie) +{ + kfree(ipc_pcie->imem); + kfree(ipc_pcie); +} + +static void ipc_pcie_remove(struct pci_dev *pci) +{ + struct iosm_pcie *ipc_pcie = pci_get_drvdata(pci); + + ipc_pcie_cleanup(ipc_pcie); + + ipc_pcie_deinit(ipc_pcie); +} + +static int ipc_pcie_resources_request(struct iosm_pcie *ipc_pcie) +{ + struct pci_dev *pci = ipc_pcie->pci; + u32 cap = 0; + u32 ret; + + /* Reserved PCI I/O and memory resources. + * Mark all PCI regions associated with PCI device pci as + * being reserved by owner IOSM_IPC. + */ + ret = pci_request_regions(pci, "IOSM_IPC"); + if (ret) { + dev_err(ipc_pcie->dev, "failed pci request regions"); + goto pci_request_region_fail; + } + + /* Reserve the doorbell IPC REGS memory resources. + * Remap the memory into CPU space. Arrange for the physical address + * (BAR) to be visible from this driver. + * pci_ioremap_bar() ensures that the memory is marked uncachable. + */ + ipc_pcie->ipc_regs = pci_ioremap_bar(pci, ipc_pcie->ipc_regs_bar_nr); + + if (!ipc_pcie->ipc_regs) { + dev_err(ipc_pcie->dev, "IPC REGS ioremap error"); + ret = -EBUSY; + goto ipc_regs_remap_fail; + } + + /* Reserve the MMIO scratchpad memory resources. + * Remap the memory into CPU space. Arrange for the physical address + * (BAR) to be visible from this driver. + * pci_ioremap_bar() ensures that the memory is marked uncachable. + */ + ipc_pcie->scratchpad = + pci_ioremap_bar(pci, ipc_pcie->scratchpad_bar_nr); + + if (!ipc_pcie->scratchpad) { + dev_err(ipc_pcie->dev, "doorbell scratchpad ioremap error"); + ret = -EBUSY; + goto scratch_remap_fail; + } + + /* Install the irq handler triggered by CP. */ + ret = ipc_acquire_irq(ipc_pcie); + if (ret) { + dev_err(ipc_pcie->dev, "acquiring MSI irq failed!"); + goto irq_acquire_fail; + } + + /* Enable bus-mastering for the IOSM IPC device. */ + pci_set_master(pci); + + /* Enable LTR if possible + * This is needed for L1.2! + */ + pcie_capability_read_dword(ipc_pcie->pci, PCI_EXP_DEVCAP2, &cap); + if (cap & PCI_EXP_DEVCAP2_LTR) + pcie_capability_set_word(ipc_pcie->pci, PCI_EXP_DEVCTL2, + PCI_EXP_DEVCTL2_LTR_EN); + + dev_dbg(ipc_pcie->dev, "link between AP and CP is fully on"); + + return ret; + +irq_acquire_fail: + iounmap(ipc_pcie->scratchpad); +scratch_remap_fail: + iounmap(ipc_pcie->ipc_regs); +ipc_regs_remap_fail: + pci_release_regions(pci); +pci_request_region_fail: + return ret; +} + +bool ipc_pcie_check_aspm_enabled(struct iosm_pcie *ipc_pcie, + bool parent) +{ + struct pci_dev *pdev; + u16 value = 0; + u32 enabled; + + if (parent) + pdev = ipc_pcie->pci->bus->self; + else + pdev = ipc_pcie->pci; + + pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &value); + enabled = value & PCI_EXP_LNKCTL_ASPMC; + dev_dbg(ipc_pcie->dev, "ASPM L1: 0x%04X 0x%03X", pdev->device, value); + + return (enabled == PCI_EXP_LNKCTL_ASPM_L1 || + enabled == PCI_EXP_LNKCTL_ASPMC); +} + +bool ipc_pcie_check_data_link_active(struct iosm_pcie *ipc_pcie) +{ + struct pci_dev *parent; + u16 link_status = 0; + + if (!ipc_pcie->pci->bus || !ipc_pcie->pci->bus->self) { + dev_err(ipc_pcie->dev, "root port not found"); + return false; + } + + parent = ipc_pcie->pci->bus->self; + + pcie_capability_read_word(parent, PCI_EXP_LNKSTA, &link_status); + dev_dbg(ipc_pcie->dev, "Link status: 0x%04X", link_status); + + return link_status & PCI_EXP_LNKSTA_DLLLA; +} + +static bool ipc_pcie_check_aspm_supported(struct iosm_pcie *ipc_pcie, + bool parent) +{ + struct pci_dev *pdev; + u32 support; + u32 cap = 0; + + if (parent) + pdev = ipc_pcie->pci->bus->self; + else + pdev = ipc_pcie->pci; + pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &cap); + support = u32_get_bits(cap, PCI_EXP_LNKCAP_ASPMS); + if (support < PCI_EXP_LNKCTL_ASPM_L1) { + dev_dbg(ipc_pcie->dev, "ASPM L1 not supported: 0x%04X", + pdev->device); + return false; + } + return true; +} + +void ipc_pcie_config_aspm(struct iosm_pcie *ipc_pcie) +{ + bool parent_aspm_enabled, dev_aspm_enabled; + + /* check if both root port and child supports ASPM L1 */ + if (!ipc_pcie_check_aspm_supported(ipc_pcie, true) || + !ipc_pcie_check_aspm_supported(ipc_pcie, false)) + return; + + parent_aspm_enabled = ipc_pcie_check_aspm_enabled(ipc_pcie, true); + dev_aspm_enabled = ipc_pcie_check_aspm_enabled(ipc_pcie, false); + + dev_dbg(ipc_pcie->dev, "ASPM parent: %s device: %s", + parent_aspm_enabled ? "Enabled" : "Disabled", + dev_aspm_enabled ? "Enabled" : "Disabled"); +} + +/* Initializes PCIe endpoint configuration */ +static void ipc_pcie_config_init(struct iosm_pcie *ipc_pcie) +{ + /* BAR0 is used for doorbell */ + ipc_pcie->ipc_regs_bar_nr = IPC_DOORBELL_BAR0; + + /* update HW configuration */ + ipc_pcie->scratchpad_bar_nr = IPC_SCRATCHPAD_BAR2; + ipc_pcie->doorbell_reg_offset = IPC_DOORBELL_CH_OFFSET; + ipc_pcie->doorbell_write = IPC_WRITE_PTR_REG_0; + ipc_pcie->doorbell_capture = IPC_CAPTURE_PTR_REG_0; +} + +/* This will read the BIOS WWAN RTD3 settings: + * D0L1.2/D3L2/Disabled + */ +static enum ipc_pcie_sleep_state ipc_pcie_read_bios_cfg(struct device *dev) +{ + union acpi_object *object; + acpi_handle handle_acpi; + + handle_acpi = ACPI_HANDLE(dev); + if (!handle_acpi) { + pr_debug("pci device is NOT ACPI supporting device\n"); + goto default_ret; + } + + object = acpi_evaluate_dsm(handle_acpi, &wwan_acpi_guid, 0, 3, NULL); + + if (object && object->integer.value == 3) + return IPC_PCIE_D3L2; + +default_ret: + return IPC_PCIE_D0L12; +} + +static int ipc_pcie_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct iosm_pcie *ipc_pcie = kzalloc(sizeof(*ipc_pcie), GFP_KERNEL); + + pr_debug("Probing device 0x%X from the vendor 0x%X", pci_id->device, + pci_id->vendor); + + if (!ipc_pcie) + goto ret_fail; + + /* Initialize ipc dbg component for the PCIe device */ + ipc_pcie->dev = &pci->dev; + + /* Set the driver specific data. */ + pci_set_drvdata(pci, ipc_pcie); + + /* Save the address of the PCI device configuration. */ + ipc_pcie->pci = pci; + + /* Update platform configuration */ + ipc_pcie_config_init(ipc_pcie); + + /* Initialize the device before it is used. Ask low-level code + * to enable I/O and memory. Wake up the device if it was suspended. + */ + if (pci_enable_device(pci)) { + dev_err(ipc_pcie->dev, "failed to enable the AP PCIe device"); + /* If enable of PCIe device has failed then calling + * ipc_pcie_cleanup will panic the system. More over + * ipc_pcie_cleanup() is required to be called after + * ipc_imem_mount() + */ + goto pci_enable_fail; + } + + ipc_pcie_config_aspm(ipc_pcie); + dev_dbg(ipc_pcie->dev, "PCIe device enabled."); + + /* Read WWAN RTD3 BIOS Setting + */ + ipc_pcie->d3l2_support = ipc_pcie_read_bios_cfg(&pci->dev); + + ipc_pcie->suspend = 0; + + if (ipc_pcie_resources_request(ipc_pcie)) + goto resources_req_fail; + + /* Establish the link to the imem layer. */ + ipc_pcie->imem = ipc_imem_init(ipc_pcie, pci->device, + ipc_pcie->scratchpad, ipc_pcie->dev); + if (!ipc_pcie->imem) { + dev_err(ipc_pcie->dev, "failed to init imem"); + goto imem_init_fail; + } + + return 0; + +imem_init_fail: + ipc_pcie_resources_release(ipc_pcie); +resources_req_fail: + pci_disable_device(pci); +pci_enable_fail: + kfree(ipc_pcie); +ret_fail: + return -EIO; +} + +static const struct pci_device_id iosm_ipc_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7560_ID) }, + {} +}; + +/* Enter sleep in s2idle case + */ +static int __maybe_unused ipc_pcie_suspend_s2idle(struct iosm_pcie *ipc_pcie) +{ + ipc_cp_irq_sleep_control(ipc_pcie, IPC_MEM_DEV_PM_FORCE_SLEEP); + + /* Complete all memory stores before setting bit */ + smp_mb__before_atomic(); + + set_bit(0, &ipc_pcie->suspend); + + /* Complete all memory stores after setting bit */ + smp_mb__after_atomic(); + + ipc_imem_pm_s2idle_sleep(ipc_pcie->imem, true); + + return 0; +} + +/* Resume from sleep in s2idle case + */ +static int __maybe_unused ipc_pcie_resume_s2idle(struct iosm_pcie *ipc_pcie) +{ + ipc_cp_irq_sleep_control(ipc_pcie, IPC_MEM_DEV_PM_FORCE_ACTIVE); + + ipc_imem_pm_s2idle_sleep(ipc_pcie->imem, false); + + /* Complete all memory stores before clearing bit. */ + smp_mb__before_atomic(); + + clear_bit(0, &ipc_pcie->suspend); + + /* Complete all memory stores after clearing bit. */ + smp_mb__after_atomic(); + return 0; +} + +int __maybe_unused ipc_pcie_suspend(struct iosm_pcie *ipc_pcie) +{ + struct pci_dev *pdev; + int ret; + + pdev = ipc_pcie->pci; + + /* Execute D3 one time. */ + if (pdev->current_state != PCI_D0) { + dev_dbg(ipc_pcie->dev, "done for PM=%d", pdev->current_state); + return 0; + } + + /* The HAL shall ask the shared memory layer whether D3 is allowed. */ + ipc_imem_pm_suspend(ipc_pcie->imem); + + /* Save the PCI configuration space of a device before suspending. */ + ret = pci_save_state(pdev); + + if (ret) { + dev_err(ipc_pcie->dev, "pci_save_state error=%d", ret); + return ret; + } + + /* Set the power state of a PCI device. + * Transition a device to a new power state, using the device's PCI PM + * registers. + */ + ret = pci_set_power_state(pdev, PCI_D3cold); + + if (ret) { + dev_err(ipc_pcie->dev, "pci_set_power_state error=%d", ret); + return ret; + } + + dev_dbg(ipc_pcie->dev, "SUSPEND done"); + return ret; +} + +int __maybe_unused ipc_pcie_resume(struct iosm_pcie *ipc_pcie) +{ + int ret; + + /* Set the power state of a PCI device. + * Transition a device to a new power state, using the device's PCI PM + * registers. + */ + ret = pci_set_power_state(ipc_pcie->pci, PCI_D0); + + if (ret) { + dev_err(ipc_pcie->dev, "pci_set_power_state error=%d", ret); + return ret; + } + + pci_restore_state(ipc_pcie->pci); + + /* The HAL shall inform the shared memory layer that the device is + * active. + */ + ipc_imem_pm_resume(ipc_pcie->imem); + + dev_dbg(ipc_pcie->dev, "RESUME done"); + return ret; +} + +static int __maybe_unused ipc_pcie_suspend_cb(struct device *dev) +{ + struct iosm_pcie *ipc_pcie; + struct pci_dev *pdev; + + pdev = to_pci_dev(dev); + + ipc_pcie = pci_get_drvdata(pdev); + + switch (ipc_pcie->d3l2_support) { + case IPC_PCIE_D0L12: + ipc_pcie_suspend_s2idle(ipc_pcie); + break; + case IPC_PCIE_D3L2: + ipc_pcie_suspend(ipc_pcie); + break; + } + + return 0; +} + +static int __maybe_unused ipc_pcie_resume_cb(struct device *dev) +{ + struct iosm_pcie *ipc_pcie; + struct pci_dev *pdev; + + pdev = to_pci_dev(dev); + + ipc_pcie = pci_get_drvdata(pdev); + + switch (ipc_pcie->d3l2_support) { + case IPC_PCIE_D0L12: + ipc_pcie_resume_s2idle(ipc_pcie); + break; + case IPC_PCIE_D3L2: + ipc_pcie_resume(ipc_pcie); + break; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(iosm_ipc_pm, ipc_pcie_suspend_cb, ipc_pcie_resume_cb); + +static struct pci_driver iosm_ipc_driver = { + .name = KBUILD_MODNAME, + .probe = ipc_pcie_probe, + .remove = ipc_pcie_remove, + .driver = { + .pm = &iosm_ipc_pm, + }, + .id_table = iosm_ipc_ids, +}; + +int ipc_pcie_addr_map(struct iosm_pcie *ipc_pcie, unsigned char *data, + size_t size, dma_addr_t *mapping, int direction) +{ + if (ipc_pcie->pci) { + *mapping = dma_map_single(&ipc_pcie->pci->dev, data, size, + direction); + if (dma_mapping_error(&ipc_pcie->pci->dev, *mapping)) { + dev_err(ipc_pcie->dev, "dma mapping failed"); + return -EINVAL; + } + } + return 0; +} + +void ipc_pcie_addr_unmap(struct iosm_pcie *ipc_pcie, size_t size, + dma_addr_t mapping, int direction) +{ + if (!mapping) + return; + if (ipc_pcie->pci) + dma_unmap_single(&ipc_pcie->pci->dev, mapping, size, direction); +} + +struct sk_buff *ipc_pcie_alloc_local_skb(struct iosm_pcie *ipc_pcie, + gfp_t flags, size_t size) +{ + struct sk_buff *skb; + + if (!ipc_pcie || !size) { + pr_err("invalid pcie object or size"); + return NULL; + } + + skb = __netdev_alloc_skb(NULL, size, flags); + if (!skb) + return NULL; + + IPC_CB(skb)->op_type = (u8)UL_DEFAULT; + IPC_CB(skb)->mapping = 0; + + return skb; +} + +struct sk_buff *ipc_pcie_alloc_skb(struct iosm_pcie *ipc_pcie, size_t size, + gfp_t flags, dma_addr_t *mapping, + int direction, size_t headroom) +{ + struct sk_buff *skb = ipc_pcie_alloc_local_skb(ipc_pcie, flags, + size + headroom); + if (!skb) + return NULL; + + if (headroom) + skb_reserve(skb, headroom); + + if (ipc_pcie_addr_map(ipc_pcie, skb->data, size, mapping, direction)) { + dev_kfree_skb(skb); + return NULL; + } + + BUILD_BUG_ON(sizeof(*IPC_CB(skb)) > sizeof(skb->cb)); + + /* Store the mapping address in skb scratch pad for later usage */ + IPC_CB(skb)->mapping = *mapping; + IPC_CB(skb)->direction = direction; + IPC_CB(skb)->len = size; + + return skb; +} + +void ipc_pcie_kfree_skb(struct iosm_pcie *ipc_pcie, struct sk_buff *skb) +{ + if (!skb) + return; + + ipc_pcie_addr_unmap(ipc_pcie, IPC_CB(skb)->len, IPC_CB(skb)->mapping, + IPC_CB(skb)->direction); + IPC_CB(skb)->mapping = 0; + dev_kfree_skb(skb); +} + +static int __init iosm_ipc_driver_init(void) +{ + if (pci_register_driver(&iosm_ipc_driver)) { + pr_err("registering of IOSM PCIe driver failed"); + return -1; + } + + if (rtnl_link_register(&iosm_netlink)) { + pr_err("IOSM RTNL register failed"); + pci_unregister_driver(&iosm_ipc_driver); + return -1; + } + + return 0; +} + +static void __exit iosm_ipc_driver_exit(void) +{ + rtnl_link_unregister(&iosm_netlink); + pci_unregister_driver(&iosm_ipc_driver); +} + +module_init(iosm_ipc_driver_init); +module_exit(iosm_ipc_driver_exit); diff --git a/drivers/net/wwan/iosm/iosm_ipc_pcie.h b/drivers/net/wwan/iosm/iosm_ipc_pcie.h new file mode 100644 index 000000000000..839809fee3dd --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_pcie.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_PCIE_H +#define IOSM_IPC_PCIE_H + +#include +#include +#include + +#include "iosm_ipc_irq.h" + +extern struct rtnl_link_ops iosm_netlink; + +/* Device ID */ +#define INTEL_CP_DEVICE_7560_ID 0x7560 + +/* Define for BAR area usage */ +#define IPC_DOORBELL_BAR0 0 +#define IPC_SCRATCHPAD_BAR2 2 + +/* Defines for DOORBELL registers information */ +#define IPC_DOORBELL_CH_OFFSET BIT(5) +#define IPC_WRITE_PTR_REG_0 BIT(4) +#define IPC_CAPTURE_PTR_REG_0 BIT(3) + +/* Number of MSI used for IPC */ +#define IPC_MSI_VECTORS 1 + +/* Total number of Maximum IPC IRQ vectors used for IPC */ +#define IPC_IRQ_VECTORS IPC_MSI_VECTORS + +/** + * enum ipc_pcie_sleep_state - Enum type to different sleep state transitions + * @IPC_PCIE_D0L12: Put the sleep state in D0L12 + * @IPC_PCIE_D3L2: Put the sleep state in D3L2 + */ +enum ipc_pcie_sleep_state { + IPC_PCIE_D0L12, + IPC_PCIE_D3L2, +}; + +/** + * struct iosm_pcie - IPC_PCIE struct. + * @pci: Address of the device description + * @dev: Pointer to generic device structure + * @ipc_regs: Remapped CP doorbell address of the irq register + * set, to fire the doorbell irq. + * @scratchpad: Remapped CP scratchpad address, to send the + * configuration. tuple and the IPC descriptors + * to CP in the ROM phase. The config tuple + * information are saved on the MSI scratchpad. + * @imem: Pointer to imem data struct + * @ipc_regs_bar_nr: BAR number to be used for IPC doorbell + * @scratchpad_bar_nr: BAR number to be used for Scratchpad + * @nvec: number of requested irq vectors + * @doorbell_reg_offset: doorbell_reg_offset + * @doorbell_write: doorbell write register + * @doorbell_capture: doorbell capture resgister + * @suspend: S2IDLE sleep/active + * @d3l2_support: Read WWAN RTD3 BIOS setting for D3L2 support + */ +struct iosm_pcie { + struct pci_dev *pci; + struct device *dev; + void __iomem *ipc_regs; + void __iomem *scratchpad; + struct iosm_imem *imem; + int ipc_regs_bar_nr; + int scratchpad_bar_nr; + int nvec; + u32 doorbell_reg_offset; + u32 doorbell_write; + u32 doorbell_capture; + unsigned long suspend; + enum ipc_pcie_sleep_state d3l2_support; +}; + +/** + * struct ipc_skb_cb - Struct definition of the socket buffer which is mapped to + * the cb field of sbk + * @mapping: Store physical or IOVA mapped address of skb virtual add. + * @direction: DMA direction + * @len: Length of the DMA mapped region + * @op_type: Expected values are defined about enum ipc_ul_usr_op. + */ +struct ipc_skb_cb { + dma_addr_t mapping; + int direction; + int len; + u8 op_type; +}; + +/** + * enum ipc_ul_usr_op - Control operation to execute the right action on + * the user interface. + * @UL_USR_OP_BLOCKED: The uplink app was blocked until CP confirms that the + * uplink buffer was consumed triggered by the IRQ. + * @UL_MUX_OP_ADB: In MUX mode the UL ADB shall be addedd to the free list. + * @UL_DEFAULT: SKB in non muxing mode + */ +enum ipc_ul_usr_op { + UL_USR_OP_BLOCKED, + UL_MUX_OP_ADB, + UL_DEFAULT, +}; + +/** + * ipc_pcie_addr_map - Maps the kernel's virtual address to either IOVA + * address space or Physical address space, the mapping is + * stored in the skb's cb. + * @ipc_pcie: Pointer to struct iosm_pcie + * @data: Skb mem containing data + * @size: Data size + * @mapping: Dma mapping address + * @direction: Data direction + * + * Returns: 0 on success and failure value on error + */ +int ipc_pcie_addr_map(struct iosm_pcie *ipc_pcie, unsigned char *data, + size_t size, dma_addr_t *mapping, int direction); + +/** + * ipc_pcie_addr_unmap - Unmaps the skb memory region from IOVA address space + * @ipc_pcie: Pointer to struct iosm_pcie + * @size: Data size + * @mapping: Dma mapping address + * @direction: Data direction + */ +void ipc_pcie_addr_unmap(struct iosm_pcie *ipc_pcie, size_t size, + dma_addr_t mapping, int direction); + +/** + * ipc_pcie_alloc_skb - Allocate an uplink SKB for the given size. + * @ipc_pcie: Pointer to struct iosm_pcie + * @size: Size of the SKB required. + * @flags: Allocation flags + * @mapping: Copies either mapped IOVA add. or converted Phy address + * @direction: DMA data direction + * @headroom: Header data offset + * + * Returns: Pointer to ipc_skb on Success, NULL on failure. + */ +struct sk_buff *ipc_pcie_alloc_skb(struct iosm_pcie *ipc_pcie, size_t size, + gfp_t flags, dma_addr_t *mapping, + int direction, size_t headroom); + +/** + * ipc_pcie_alloc_local_skb - Allocate a local SKB for the given size. + * @ipc_pcie: Pointer to struct iosm_pcie + * @flags: Allocation flags + * @size: Size of the SKB required. + * + * Returns: Pointer to ipc_skb on Success, NULL on failure. + */ +struct sk_buff *ipc_pcie_alloc_local_skb(struct iosm_pcie *ipc_pcie, + gfp_t flags, size_t size); + +/** + * ipc_pcie_kfree_skb - Free skb allocated by ipc_pcie_alloc_*_skb(). + * @ipc_pcie: Pointer to struct iosm_pcie + * @skb: Pointer to the skb + */ +void ipc_pcie_kfree_skb(struct iosm_pcie *ipc_pcie, struct sk_buff *skb); + +/** + * ipc_pcie_check_data_link_active - Check Data Link Layer Active + * @ipc_pcie: Pointer to struct iosm_pcie + * + * Returns: true if active, otherwise false + */ +bool ipc_pcie_check_data_link_active(struct iosm_pcie *ipc_pcie); + +/** + * ipc_pcie_suspend - Callback invoked by pm_runtime_suspend. It decrements + * the device's usage count then, carry out a suspend, + * either synchronous or asynchronous. + * @ipc_pcie: Pointer to struct iosm_pcie + * + * Returns: 0 on success and failure value on error + */ +int ipc_pcie_suspend(struct iosm_pcie *ipc_pcie); + +/** + * ipc_pcie_resume - Callback invoked by pm_runtime_resume. It increments + * the device's usage count then, carry out a resume, + * either synchronous or asynchronous. + * @ipc_pcie: Pointer to struct iosm_pcie + * + * Returns: 0 on success and failure value on error + */ +int ipc_pcie_resume(struct iosm_pcie *ipc_pcie); + +/** + * ipc_pcie_check_aspm_enabled - Check if ASPM L1 is already enabled + * @ipc_pcie: Pointer to struct iosm_pcie + * @parent: True if checking ASPM L1 for parent else false + * + * Returns: true if ASPM is already enabled else false + */ +bool ipc_pcie_check_aspm_enabled(struct iosm_pcie *ipc_pcie, + bool parent); +/** + * ipc_pcie_config_aspm - Configure ASPM L1 + * @ipc_pcie: Pointer to struct iosm_pcie + */ +void ipc_pcie_config_aspm(struct iosm_pcie *ipc_pcie); + +#endif From patchwork Thu May 20 14:01:45 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443857 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 97CB4C43460 for ; Thu, 20 May 2021 14:06:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 76AC36121E for ; Thu, 20 May 2021 14:06:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243593AbhETOHk (ORCPT ); Thu, 20 May 2021 10:07:40 -0400 Received: from mga11.intel.com ([192.55.52.93]:5245 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S242098AbhETOGX (ORCPT ); Thu, 20 May 2021 10:06:23 -0400 IronPort-SDR: FB4DaaaFy3VUU+vyOeFV6iWKaRJoWuv3j78TReGq6dLTh0rnPshbL6pxUAUg9n6faEXtGlJOyt Q6+CjGMC5UrA== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144554" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144554" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:02:33 -0700 IronPort-SDR: AUcBomBuwx9DkosXt1lRnFIVr7npYZVj2absZcdTdR2kldkkPYbmGlUfzi0OlURcIJ8+B3c5SM yIh0xVcxbuZg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407494" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:02:32 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 03/16] net: iosm: mmio scratchpad Date: Thu, 20 May 2021 19:31:45 +0530 Message-Id: <20210520140158.10132-4-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 1) Initializes the Scratchpad region for Host-Device communication. 2) Exposes device capabilities like chip info and device execution stages. Signed-off-by: M Chetan Kumar --- v3: no change. v2: * Removed space around the : for the bitfields. * Return proper error code instead of returning -1. --- drivers/net/wwan/iosm/iosm_ipc_mmio.c | 223 ++++++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_mmio.h | 193 ++++++++++++++++++++++ 2 files changed, 416 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_mmio.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_mmio.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_mmio.c b/drivers/net/wwan/iosm/iosm_ipc_mmio.c new file mode 100644 index 000000000000..06c94b1720b6 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_mmio.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include +#include +#include +#include +#include + +#include "iosm_ipc_mmio.h" + +/* Definition of MMIO offsets + * note that MMIO_CI offsets are relative to end of chip info structure + */ + +/* MMIO chip info size in bytes */ +#define MMIO_CHIP_INFO_SIZE 60 + +/* CP execution stage */ +#define MMIO_OFFSET_EXECUTION_STAGE 0x00 + +/* Boot ROM Chip Info struct */ +#define MMIO_OFFSET_CHIP_INFO 0x04 + +#define MMIO_OFFSET_ROM_EXIT_CODE 0x40 + +#define MMIO_OFFSET_PSI_ADDRESS 0x54 + +#define MMIO_OFFSET_PSI_SIZE 0x5C + +#define MMIO_OFFSET_IPC_STATUS 0x60 + +#define MMIO_OFFSET_CONTEXT_INFO 0x64 + +#define MMIO_OFFSET_BASE_ADDR 0x6C + +#define MMIO_OFFSET_END_ADDR 0x74 + +#define MMIO_OFFSET_CP_VERSION 0xF0 + +#define MMIO_OFFSET_CP_CAPABILITIES 0xF4 + +/* Timeout in 50 msec to wait for the modem boot code to write a valid + * execution stage into mmio area + */ +#define IPC_MMIO_EXEC_STAGE_TIMEOUT 50 + +/* check if exec stage has one of the valid values */ +static bool ipc_mmio_is_valid_exec_stage(enum ipc_mem_exec_stage stage) +{ + switch (stage) { + case IPC_MEM_EXEC_STAGE_BOOT: + case IPC_MEM_EXEC_STAGE_PSI: + case IPC_MEM_EXEC_STAGE_EBL: + case IPC_MEM_EXEC_STAGE_RUN: + case IPC_MEM_EXEC_STAGE_CRASH: + case IPC_MEM_EXEC_STAGE_CD_READY: + return true; + default: + return false; + } +} + +void ipc_mmio_update_cp_capability(struct iosm_mmio *ipc_mmio) +{ + u32 cp_cap; + unsigned int ver; + + ver = ipc_mmio_get_cp_version(ipc_mmio); + cp_cap = readl(ipc_mmio->base + ipc_mmio->offset.cp_capability); + + ipc_mmio->has_mux_lite = (ver >= IOSM_CP_VERSION) && + !(cp_cap & DL_AGGR) && !(cp_cap & UL_AGGR); + + ipc_mmio->has_ul_flow_credit = + (ver >= IOSM_CP_VERSION) && (cp_cap & UL_FLOW_CREDIT); +} + +struct iosm_mmio *ipc_mmio_init(void __iomem *mmio, struct device *dev) +{ + struct iosm_mmio *ipc_mmio = kzalloc(sizeof(*ipc_mmio), GFP_KERNEL); + int retries = IPC_MMIO_EXEC_STAGE_TIMEOUT; + enum ipc_mem_exec_stage stage; + + if (!ipc_mmio) + return NULL; + + ipc_mmio->dev = dev; + + ipc_mmio->base = mmio; + + ipc_mmio->offset.exec_stage = MMIO_OFFSET_EXECUTION_STAGE; + + /* Check for a valid execution stage to make sure that the boot code + * has correctly initialized the MMIO area. + */ + do { + stage = ipc_mmio_get_exec_stage(ipc_mmio); + if (ipc_mmio_is_valid_exec_stage(stage)) + break; + + msleep(20); + } while (retries-- > 0); + + if (!retries) { + dev_err(ipc_mmio->dev, "invalid exec stage %X", stage); + goto init_fail; + } + + ipc_mmio->offset.chip_info = MMIO_OFFSET_CHIP_INFO; + + /* read chip info size and version from chip info structure */ + ipc_mmio->chip_info_version = + ioread8(ipc_mmio->base + ipc_mmio->offset.chip_info); + + /* Increment of 2 is needed as the size value in the chip info + * excludes the version and size field, which are always present + */ + ipc_mmio->chip_info_size = + ioread8(ipc_mmio->base + ipc_mmio->offset.chip_info + 1) + 2; + + if (ipc_mmio->chip_info_size != MMIO_CHIP_INFO_SIZE) { + dev_err(ipc_mmio->dev, "Unexpected Chip Info"); + goto init_fail; + } + + ipc_mmio->offset.rom_exit_code = MMIO_OFFSET_ROM_EXIT_CODE; + + ipc_mmio->offset.psi_address = MMIO_OFFSET_PSI_ADDRESS; + ipc_mmio->offset.psi_size = MMIO_OFFSET_PSI_SIZE; + ipc_mmio->offset.ipc_status = MMIO_OFFSET_IPC_STATUS; + ipc_mmio->offset.context_info = MMIO_OFFSET_CONTEXT_INFO; + ipc_mmio->offset.ap_win_base = MMIO_OFFSET_BASE_ADDR; + ipc_mmio->offset.ap_win_end = MMIO_OFFSET_END_ADDR; + + ipc_mmio->offset.cp_version = MMIO_OFFSET_CP_VERSION; + ipc_mmio->offset.cp_capability = MMIO_OFFSET_CP_CAPABILITIES; + + return ipc_mmio; + +init_fail: + kfree(ipc_mmio); + return NULL; +} + +enum ipc_mem_exec_stage ipc_mmio_get_exec_stage(struct iosm_mmio *ipc_mmio) +{ + if (!ipc_mmio) + return IPC_MEM_EXEC_STAGE_INVALID; + + return (enum ipc_mem_exec_stage)readl(ipc_mmio->base + + ipc_mmio->offset.exec_stage); +} + +void ipc_mmio_copy_chip_info(struct iosm_mmio *ipc_mmio, void *dest, + size_t size) +{ + if (ipc_mmio && dest) + memcpy_fromio(dest, ipc_mmio->base + ipc_mmio->offset.chip_info, + size); +} + +enum ipc_mem_device_ipc_state ipc_mmio_get_ipc_state(struct iosm_mmio *ipc_mmio) +{ + if (!ipc_mmio) + return IPC_MEM_DEVICE_IPC_INVALID; + + return (enum ipc_mem_device_ipc_state) + readl(ipc_mmio->base + ipc_mmio->offset.ipc_status); +} + +enum rom_exit_code ipc_mmio_get_rom_exit_code(struct iosm_mmio *ipc_mmio) +{ + if (!ipc_mmio) + return IMEM_ROM_EXIT_FAIL; + + return (enum rom_exit_code)readl(ipc_mmio->base + + ipc_mmio->offset.rom_exit_code); +} + +void ipc_mmio_config(struct iosm_mmio *ipc_mmio) +{ + if (!ipc_mmio) + return; + + /* AP memory window (full window is open and active so that modem checks + * each AP address) 0 means don't check on modem side. + */ + iowrite64_lo_hi(0, ipc_mmio->base + ipc_mmio->offset.ap_win_base); + iowrite64_lo_hi(0, ipc_mmio->base + ipc_mmio->offset.ap_win_end); + + iowrite64_lo_hi(ipc_mmio->context_info_addr, + ipc_mmio->base + ipc_mmio->offset.context_info); +} + +void ipc_mmio_set_psi_addr_and_size(struct iosm_mmio *ipc_mmio, dma_addr_t addr, + u32 size) +{ + if (!ipc_mmio) + return; + + iowrite64_lo_hi(addr, ipc_mmio->base + ipc_mmio->offset.psi_address); + writel(size, ipc_mmio->base + ipc_mmio->offset.psi_size); +} + +void ipc_mmio_set_contex_info_addr(struct iosm_mmio *ipc_mmio, phys_addr_t addr) +{ + if (!ipc_mmio) + return; + + /* store context_info address. This will be stored in the mmio area + * during IPC_MEM_DEVICE_IPC_INIT state via ipc_mmio_config() + */ + ipc_mmio->context_info_addr = addr; +} + +int ipc_mmio_get_cp_version(struct iosm_mmio *ipc_mmio) +{ + return ipc_mmio ? readl(ipc_mmio->base + ipc_mmio->offset.cp_version) : + -EFAULT; +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_mmio.h b/drivers/net/wwan/iosm/iosm_ipc_mmio.h new file mode 100644 index 000000000000..bcf77aea06e7 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_mmio.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_MMIO_H +#define IOSM_IPC_MMIO_H + +/* Minimal IOSM CP VERSION which has valid CP_CAPABILITIES field */ +#define IOSM_CP_VERSION 0x0100UL + +/* DL dir Aggregation support mask */ +#define DL_AGGR BIT(23) + +/* UL dir Aggregation support mask */ +#define UL_AGGR BIT(22) + +/* UL flow credit support mask */ +#define UL_FLOW_CREDIT BIT(21) + +/* Possible states of the IPC finite state machine. */ +enum ipc_mem_device_ipc_state { + IPC_MEM_DEVICE_IPC_UNINIT, + IPC_MEM_DEVICE_IPC_INIT, + IPC_MEM_DEVICE_IPC_RUNNING, + IPC_MEM_DEVICE_IPC_RECOVERY, + IPC_MEM_DEVICE_IPC_ERROR, + IPC_MEM_DEVICE_IPC_DONT_CARE, + IPC_MEM_DEVICE_IPC_INVALID = -1 +}; + +/* Boot ROM exit status. */ +enum rom_exit_code { + IMEM_ROM_EXIT_OPEN_EXT = 0x01, + IMEM_ROM_EXIT_OPEN_MEM = 0x02, + IMEM_ROM_EXIT_CERT_EXT = 0x10, + IMEM_ROM_EXIT_CERT_MEM = 0x20, + IMEM_ROM_EXIT_FAIL = 0xFF +}; + +/* Boot stages */ +enum ipc_mem_exec_stage { + IPC_MEM_EXEC_STAGE_RUN = 0x600DF00D, + IPC_MEM_EXEC_STAGE_CRASH = 0x8BADF00D, + IPC_MEM_EXEC_STAGE_CD_READY = 0xBADC0DED, + IPC_MEM_EXEC_STAGE_BOOT = 0xFEEDB007, + IPC_MEM_EXEC_STAGE_PSI = 0xFEEDBEEF, + IPC_MEM_EXEC_STAGE_EBL = 0xFEEDCAFE, + IPC_MEM_EXEC_STAGE_INVALID = 0xFFFFFFFF +}; + +/* mmio scratchpad info */ +struct mmio_offset { + int exec_stage; + int chip_info; + int rom_exit_code; + int psi_address; + int psi_size; + int ipc_status; + int context_info; + int ap_win_base; + int ap_win_end; + int cp_version; + int cp_capability; +}; + +/** + * struct iosm_mmio - MMIO region mapped to the doorbell scratchpad. + * @base: Base address of MMIO region + * @dev: Pointer to device structure + * @offset: Start offset + * @context_info_addr: Physical base address of context info structure + * @chip_info_version: Version of chip info structure + * @chip_info_size: Size of chip info structure + * @has_mux_lite: It doesn't support mux aggergation + * @has_ul_flow_credit: Ul flow credit support + * @has_slp_no_prot: Device sleep no protocol support + * @has_mcr_support: Usage of mcr support + */ +struct iosm_mmio { + unsigned char __iomem *base; + struct device *dev; + struct mmio_offset offset; + phys_addr_t context_info_addr; + unsigned int chip_info_version; + unsigned int chip_info_size; + u8 has_mux_lite:1, + has_ul_flow_credit:1, + has_slp_no_prot:1, + has_mcr_support:1; +}; + +/** + * ipc_mmio_init - Allocate mmio instance data + * @mmio_addr: Mapped AP base address of the MMIO area. + * @dev: Pointer to device structure + * + * Returns: address of mmio instance data or NULL if fails. + */ +struct iosm_mmio *ipc_mmio_init(void __iomem *mmio_addr, struct device *dev); + +/** + * ipc_mmio_set_psi_addr_and_size - Set start address and size of the + * primary system image (PSI) for the + * FW dowload. + * @ipc_mmio: Pointer to mmio instance + * @addr: PSI address + * @size: PSI immage size + */ +void ipc_mmio_set_psi_addr_and_size(struct iosm_mmio *ipc_mmio, dma_addr_t addr, + u32 size); + +/** + * ipc_mmio_set_contex_info_addr - Stores the Context Info Address in + * MMIO instance to share it with CP during + * mmio_init. + * @ipc_mmio: Pointer to mmio instance + * @addr: 64-bit address of AP context information. + */ +void ipc_mmio_set_contex_info_addr(struct iosm_mmio *ipc_mmio, + phys_addr_t addr); + +/** + * ipc_mmio_get_cp_version - Write context info and AP memory range addresses. + * This needs to be called when CP is in + * IPC_MEM_DEVICE_IPC_INIT state + * @ipc_mmio: Pointer to mmio instance + * + * Returns: cp version else failure value on error + */ +int ipc_mmio_get_cp_version(struct iosm_mmio *ipc_mmio); + +/** + * ipc_mmio_get_cp_version - Get the CP IPC version + * @ipc_mmio: Pointer to mmio instance + * + * Returns: version number on success and failure value on error. + */ +int ipc_mmio_get_cp_version(struct iosm_mmio *ipc_mmio); + +/** + * ipc_mmio_get_rom_exit_code - Get exit code from CP boot rom download app + * @ipc_mmio: Pointer to mmio instance + * + * Returns: exit code from CP boot rom download APP + */ +enum rom_exit_code ipc_mmio_get_rom_exit_code(struct iosm_mmio *ipc_mmio); + +/** + * ipc_mmio_get_exec_stage - Query CP execution stage + * @ipc_mmio: Pointer to mmio instance + * + * Returns: CP execution stage + */ +enum ipc_mem_exec_stage ipc_mmio_get_exec_stage(struct iosm_mmio *ipc_mmio); + +/** + * ipc_mmio_get_ipc_state - Query CP IPC state + * @ipc_mmio: Pointer to mmio instance + * + * Returns: CP IPC state + */ +enum ipc_mem_device_ipc_state +ipc_mmio_get_ipc_state(struct iosm_mmio *ipc_mmio); + +/** + * ipc_mmio_copy_chip_info - Copy size bytes of CP chip info structure + * into caller provided buffer + * @ipc_mmio: Pointer to mmio instance + * @dest: Pointer to caller provided buff + * @size: Number of bytes to copy + */ +void ipc_mmio_copy_chip_info(struct iosm_mmio *ipc_mmio, void *dest, + size_t size); + +/** + * ipc_mmio_config - Write context info and AP memory range addresses. + * This needs to be called when CP is in + * IPC_MEM_DEVICE_IPC_INIT state + * + * @ipc_mmio: Pointer to mmio instance + */ +void ipc_mmio_config(struct iosm_mmio *ipc_mmio); + +/** + * ipc_mmio_update_cp_capability - Read and update modem capability, from mmio + * capability offset + * + * @ipc_mmio: Pointer to mmio instance + */ +void ipc_mmio_update_cp_capability(struct iosm_mmio *ipc_mmio); + +#endif From patchwork Thu May 20 14:01:47 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443856 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 819E7C433ED for ; Thu, 20 May 2021 14:06:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6546060FE7 for ; Thu, 20 May 2021 14:06:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235717AbhETOH7 (ORCPT ); Thu, 20 May 2021 10:07:59 -0400 Received: from mga11.intel.com ([192.55.52.93]:5047 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234831AbhETOG1 (ORCPT ); Thu, 20 May 2021 10:06:27 -0400 IronPort-SDR: BP+HtXL7tjO8QDOlNw17WBY1s9X5/Am2IhPnRacBEOGHJH/NRePJGqPQ4q83uc9nZ3gaXs6Ub+ GMXJuDc3kkjQ== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144576" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144576" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:02:40 -0700 IronPort-SDR: pAV02BfRYo1pYbn7dQAJpgHzWamXzNrex2PPnQs1G33hDANdam/BEnG0E2a3Qf4tGZ+SwxOWNW z7yHyFo7OMYA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407528" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:02:38 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 05/16] net: iosm: shared memory I/O operations Date: Thu, 20 May 2021 19:31:47 +0530 Message-Id: <20210520140158.10132-6-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 1) Binds logical channel between host-device for communication. 2) Implements device specific(Char/Net) IO operations. Signed-off-by: M Chetan Kumar --- v3: * WWAN port adaptation. * Aligned ipc_ prefix for function name to be consistent across file. v2: * Change vlan_id to ip link if_id & document correction. * Define new enums for IP & DSS session mapping. * Return proper error code instead of returning -1. * Clean-up vlan tag id & removed FW flashing logic. --- drivers/net/wwan/iosm/iosm_ipc_imem_ops.c | 346 ++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_imem_ops.h | 99 +++++++ 2 files changed, 445 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_imem_ops.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_imem_ops.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c new file mode 100644 index 000000000000..46f76e8aae92 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include + +#include "iosm_ipc_chnl_cfg.h" +#include "iosm_ipc_imem.h" +#include "iosm_ipc_imem_ops.h" +#include "iosm_ipc_port.h" +#include "iosm_ipc_task_queue.h" + +/* Open a packet data online channel between the network layer and CP. */ +int ipc_imem_sys_wwan_open(struct iosm_imem *ipc_imem, int if_id) +{ + dev_dbg(ipc_imem->dev, "%s if id: %d", + ipc_imem_phase_get_string(ipc_imem->phase), if_id); + + /* The network interface is only supported in the runtime phase. */ + if (ipc_imem_phase_update(ipc_imem) != IPC_P_RUN) { + dev_err(ipc_imem->dev, "net:%d : refused phase %s", if_id, + ipc_imem_phase_get_string(ipc_imem->phase)); + return -EIO; + } + + /* check for the interafce id + * if if_id 1 to 8 then create IP MUX channel sessions. + * To start MUX session from 0 as network interface id would start + * from 1 so map it to if_id = if_id - 1 + */ + if (if_id >= IP_MUX_SESSION_START && if_id <= IP_MUX_SESSION_END) + return ipc_mux_open_session(ipc_imem->mux, if_id - 1); + + return -EINVAL; +} + +/* Release a net link to CP. */ +void ipc_imem_sys_wwan_close(struct iosm_imem *ipc_imem, int if_id, + int channel_id) +{ + if (ipc_imem->mux && if_id >= IP_MUX_SESSION_START && + if_id <= IP_MUX_SESSION_END) + ipc_mux_close_session(ipc_imem->mux, if_id - 1); +} + +/* Tasklet call to do uplink transfer. */ +static int ipc_imem_tq_cdev_write(struct iosm_imem *ipc_imem, int arg, + void *msg, size_t size) +{ + ipc_imem->ev_cdev_write_pending = false; + ipc_imem_ul_send(ipc_imem); + + return 0; +} + +/* Through tasklet to do sio write. */ +static int ipc_imem_call_cdev_write(struct iosm_imem *ipc_imem) +{ + if (ipc_imem->ev_cdev_write_pending) + return -1; + + ipc_imem->ev_cdev_write_pending = true; + + return ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_cdev_write, 0, + NULL, 0, false); +} + +/* Function for transfer UL data */ +int ipc_imem_sys_wwan_transmit(struct iosm_imem *ipc_imem, + int if_id, int channel_id, struct sk_buff *skb) +{ + int ret = -EINVAL; + + if (!ipc_imem || channel_id < 0) + goto out; + + /* Is CP Running? */ + if (ipc_imem->phase != IPC_P_RUN) { + dev_dbg(ipc_imem->dev, "phase %s transmit", + ipc_imem_phase_get_string(ipc_imem->phase)); + ret = -EIO; + goto out; + } + + if (if_id >= IP_MUX_SESSION_START && if_id <= IP_MUX_SESSION_END) + /* Route the UL packet through IP MUX Layer */ + ret = ipc_mux_ul_trigger_encode(ipc_imem->mux, + if_id - 1, skb); + else + dev_err(ipc_imem->dev, + "invalid if_id %d: ", if_id); +out: + return ret; +} + +/* Initialize wwan channel */ +void ipc_imem_wwan_channel_init(struct iosm_imem *ipc_imem, + enum ipc_mux_protocol mux_type) +{ + struct ipc_chnl_cfg chnl_cfg = { 0 }; + + ipc_imem->cp_version = ipc_mmio_get_cp_version(ipc_imem->mmio); + + /* If modem version is invalid (0xffffffff), do not initialize WWAN. */ + if (ipc_imem->cp_version == -1) { + dev_err(ipc_imem->dev, "invalid CP version"); + return; + } + + ipc_chnl_cfg_get(&chnl_cfg, ipc_imem->nr_of_channels); + ipc_imem_channel_init(ipc_imem, IPC_CTYPE_WWAN, chnl_cfg, + IRQ_MOD_OFF); + + /* WWAN registration. */ + ipc_imem->wwan = ipc_wwan_init(ipc_imem, ipc_imem->dev); + if (!ipc_imem->wwan) + dev_err(ipc_imem->dev, + "failed to register the ipc_wwan interfaces"); +} + +/* Map SKB to DMA for transfer */ +static int ipc_imem_map_skb_to_dma(struct iosm_imem *ipc_imem, + struct sk_buff *skb) +{ + struct iosm_pcie *ipc_pcie = ipc_imem->pcie; + char *buf = skb->data; + int len = skb->len; + dma_addr_t mapping; + int ret; + + ret = ipc_pcie_addr_map(ipc_pcie, buf, len, &mapping, DMA_TO_DEVICE); + + if (ret) + goto err; + + BUILD_BUG_ON(sizeof(*IPC_CB(skb)) > sizeof(skb->cb)); + + IPC_CB(skb)->mapping = mapping; + IPC_CB(skb)->direction = DMA_TO_DEVICE; + IPC_CB(skb)->len = len; + IPC_CB(skb)->op_type = (u8)UL_DEFAULT; + +err: + return ret; +} + +/* return true if channel is ready for use */ +static bool ipc_imem_is_channel_active(struct iosm_imem *ipc_imem, + struct ipc_mem_channel *channel) +{ + enum ipc_phase phase; + + /* Update the current operation phase. */ + phase = ipc_imem->phase; + + /* Select the operation depending on the execution stage. */ + switch (phase) { + case IPC_P_RUN: + case IPC_P_PSI: + case IPC_P_EBL: + break; + + case IPC_P_ROM: + /* Prepare the PSI image for the CP ROM driver and + * suspend the flash app. + */ + if (channel->state != IMEM_CHANNEL_RESERVED) { + dev_err(ipc_imem->dev, + "ch[%d]:invalid channel state %d,expected %d", + channel->channel_id, channel->state, + IMEM_CHANNEL_RESERVED); + goto channel_unavailable; + } + goto channel_available; + + default: + /* Ignore uplink actions in all other phases. */ + dev_err(ipc_imem->dev, "ch[%d]: confused phase %d", + channel->channel_id, phase); + goto channel_unavailable; + } + /* Check the full availability of the channel. */ + if (channel->state != IMEM_CHANNEL_ACTIVE) { + dev_err(ipc_imem->dev, "ch[%d]: confused channel state %d", + channel->channel_id, channel->state); + goto channel_unavailable; + } + +channel_available: + return true; + +channel_unavailable: + return false; +} + +/* Release a sio link to CP. */ +void ipc_imem_sys_cdev_close(struct iosm_cdev *ipc_cdev) +{ + struct iosm_imem *ipc_imem = ipc_cdev->ipc_imem; + struct ipc_mem_channel *channel = ipc_cdev->channel; + enum ipc_phase curr_phase; + int status = 0; + u32 tail = 0; + + curr_phase = ipc_imem->phase; + + /* If current phase is IPC_P_OFF or SIO ID is -ve then + * channel is already freed. Nothing to do. + */ + if (curr_phase == IPC_P_OFF) { + dev_err(ipc_imem->dev, + "nothing to do. Current Phase: %s", + ipc_imem_phase_get_string(curr_phase)); + return; + } + + if (channel->state == IMEM_CHANNEL_FREE) { + dev_err(ipc_imem->dev, "ch[%d]: invalid channel state %d", + channel->channel_id, channel->state); + return; + } + + /* If there are any pending TDs then wait for Timeout/Completion before + * closing pipe. + */ + if (channel->ul_pipe.old_tail != channel->ul_pipe.old_head) { + ipc_imem->app_notify_ul_pend = 1; + + /* Suspend the user app and wait a certain time for processing + * UL Data. + */ + status = wait_for_completion_interruptible_timeout + (&ipc_imem->ul_pend_sem, + msecs_to_jiffies(IPC_PEND_DATA_TIMEOUT)); + if (status == 0) { + dev_dbg(ipc_imem->dev, + "Pend data Timeout UL-Pipe:%d Head:%d Tail:%d", + channel->ul_pipe.pipe_nr, + channel->ul_pipe.old_head, + channel->ul_pipe.old_tail); + } + + ipc_imem->app_notify_ul_pend = 0; + } + + /* If there are any pending TDs then wait for Timeout/Completion before + * closing pipe. + */ + ipc_protocol_get_head_tail_index(ipc_imem->ipc_protocol, + &channel->dl_pipe, NULL, &tail); + + if (tail != channel->dl_pipe.old_tail) { + ipc_imem->app_notify_dl_pend = 1; + + /* Suspend the user app and wait a certain time for processing + * DL Data. + */ + status = wait_for_completion_interruptible_timeout + (&ipc_imem->dl_pend_sem, + msecs_to_jiffies(IPC_PEND_DATA_TIMEOUT)); + if (status == 0) { + dev_dbg(ipc_imem->dev, + "Pend data Timeout DL-Pipe:%d Head:%d Tail:%d", + channel->dl_pipe.pipe_nr, + channel->dl_pipe.old_head, + channel->dl_pipe.old_tail); + } + + ipc_imem->app_notify_dl_pend = 0; + } + + /* Due to wait for completion in messages, there is a small window + * between closing the pipe and updating the channel is closed. In this + * small window there could be HP update from Host Driver. Hence update + * the channel state as CLOSING to aviod unnecessary interrupt + * towards CP. + */ + channel->state = IMEM_CHANNEL_CLOSING; + + ipc_imem_pipe_close(ipc_imem, &channel->ul_pipe); + ipc_imem_pipe_close(ipc_imem, &channel->dl_pipe); + + ipc_imem_channel_free(channel); +} + +/* Open a PORT link to CP and return the channel */ +struct ipc_mem_channel *ipc_imem_sys_port_open(struct iosm_imem *ipc_imem, + int chl_id, int hp_id) +{ + struct ipc_mem_channel *channel; + int ch_id; + + /* The PORT interface is only supported in the runtime phase. */ + if (ipc_imem_phase_update(ipc_imem) != IPC_P_RUN) { + dev_err(ipc_imem->dev, "PORT open refused, phase %s", + ipc_imem_phase_get_string(ipc_imem->phase)); + return NULL; + } + + ch_id = ipc_imem_channel_alloc(ipc_imem, chl_id, IPC_CTYPE_CTRL); + + if (ch_id < 0) { + dev_err(ipc_imem->dev, "reservation of an PORT chnl id failed"); + return NULL; + } + + channel = ipc_imem_channel_open(ipc_imem, ch_id, hp_id); + + if (!channel) { + dev_err(ipc_imem->dev, "PORT channel id open failed"); + return NULL; + } + + return channel; +} + +/* transfer skb to modem */ +int ipc_imem_sys_cdev_write(struct iosm_cdev *ipc_cdev, struct sk_buff *skb) +{ + struct ipc_mem_channel *channel = ipc_cdev->channel; + struct iosm_imem *ipc_imem = ipc_cdev->ipc_imem; + int ret = -EIO; + + if (!ipc_imem_is_channel_active(ipc_imem, channel) || + ipc_imem->phase == IPC_P_OFF_REQ) + goto out; + + ret = ipc_imem_map_skb_to_dma(ipc_imem, skb); + + if (ret) + goto out; + + /* Add skb to the uplink skbuf accumulator. */ + skb_queue_tail(&channel->ul_list, skb); + + ret = ipc_imem_call_cdev_write(ipc_imem); + + if (ret) { + skb_dequeue_tail(&channel->ul_list); + dev_err(ipc_cdev->dev, "channel id[%d] write failed\n", + ipc_cdev->channel->channel_id); + } +out: + return ret; +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h new file mode 100644 index 000000000000..6677a82be77b --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_IMEM_OPS_H +#define IOSM_IPC_IMEM_OPS_H + +#include "iosm_ipc_mux_codec.h" + +/* Maximum wait time for blocking read */ +#define IPC_READ_TIMEOUT 500 + +/* The delay in ms for defering the unregister */ +#define SIO_UNREGISTER_DEFER_DELAY_MS 1 + +/* Default delay till CP PSI image is running and modem updates the + * execution stage. + * unit : milliseconds + */ +#define PSI_START_DEFAULT_TIMEOUT 3000 + +/* Default time out when closing SIO, till the modem is in + * running state. + * unit : milliseconds + */ +#define BOOT_CHECK_DEFAULT_TIMEOUT 400 + +/* IP MUX channel range */ +#define IP_MUX_SESSION_START 1 +#define IP_MUX_SESSION_END 8 +#define MAX_IP_MUX_SESSION IP_MUX_SESSION_END + +/** + * ipc_imem_sys_port_open - Open a port link to CP. + * @ipc_imem: Imem instance. + * @chl_id: Channel Indentifier. + * @hp_id: HP Indentifier. + * + * Return: channel instance on success, NULL for failure + */ +struct ipc_mem_channel *ipc_imem_sys_port_open(struct iosm_imem *ipc_imem, + int chl_id, int hp_id); + +/** + * ipc_imem_sys_cdev_close - Release a sio link to CP. + * @ipc_cdev: iosm sio instance. + */ +void ipc_imem_sys_cdev_close(struct iosm_cdev *ipc_cdev); + +/** + * ipc_imem_sys_cdev_write - Route the uplink buffer to CP. + * @ipc_cdev: iosm_cdev instance. + * @skb: Pointer to skb. + * + * Return: 0 on success and failure value on error + */ +int ipc_imem_sys_cdev_write(struct iosm_cdev *ipc_cdev, struct sk_buff *skb); + +/** + * ipc_imem_sys_wwan_open - Open packet data online channel between network + * layer and CP. + * @ipc_imem: Imem instance. + * @if_id: ip link tag of the net device. + * + * Return: Channel ID on success and failure value on error + */ +int ipc_imem_sys_wwan_open(struct iosm_imem *ipc_imem, int if_id); + +/** + * ipc_imem_sys_wwan_close - Close packet data online channel between network + * layer and CP. + * @ipc_imem: Imem instance. + * @if_id: IP link id net device. + * @channel_id: Channel ID to be closed. + */ +void ipc_imem_sys_wwan_close(struct iosm_imem *ipc_imem, int if_id, + int channel_id); + +/** + * ipc_imem_sys_wwan_transmit - Function for transfer UL data + * @ipc_imem: Imem instance. + * @if_id: link ID of the device. + * @channel_id: Channel ID used + * @skb: Pointer to sk buffer + * + * Return: 0 on success and failure value on error + */ +int ipc_imem_sys_wwan_transmit(struct iosm_imem *ipc_imem, int if_id, + int channel_id, struct sk_buff *skb); +/** + * ipc_imem_wwan_channel_init - Initializes WWAN channels and the channel for + * MUX. + * @ipc_imem: Pointer to iosm_imem struct. + * @mux_type: Type of mux protocol. + */ +void ipc_imem_wwan_channel_init(struct iosm_imem *ipc_imem, + enum ipc_mux_protocol mux_type); +#endif From patchwork Thu May 20 14:01:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443855 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8A951C43461 for ; Thu, 20 May 2021 14:07:34 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 71E4E6100A for ; Thu, 20 May 2021 14:07:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243652AbhETOIv (ORCPT ); Thu, 20 May 2021 10:08:51 -0400 Received: from mga11.intel.com ([192.55.52.93]:5246 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S240011AbhETOHj (ORCPT ); Thu, 20 May 2021 10:07:39 -0400 IronPort-SDR: iq0tf0nEsKBJ7tEVCtKaAjs4JJmzp1DM9m+zPSzxqM7lYcB4w8CzOPL5yE0w0rNx1EGyMVsG9M qNAOSUtdmOvw== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144597" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144597" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:02:47 -0700 IronPort-SDR: Hn1UBBxrneZ71TeM+nRUBxmqj2BDyHDmQO6PDXAuyz4d/LYN2kmdpb9OPbE6nxHxFATi92vBBS 4dHUHpdBb9CQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407557" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:02:45 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 07/16] net: iosm: wwan port control device Date: Thu, 20 May 2021 19:31:49 +0530 Message-Id: <20210520140158.10132-8-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Implements wwan port for MBIM & AT protocol communication Signed-off-by: M Chetan Kumar --- v3: * Renamed file to iosm_ipc_port.c * WWAN PORT adaptation for AT & MBIM protocol communication. v2: * Renamed iosm_sio struct to iosm_cdev. * Added memory barriers around atomic operations. --- drivers/net/wwan/iosm/iosm_ipc_port.c | 85 +++++++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_port.h | 50 ++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_port.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_port.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_port.c b/drivers/net/wwan/iosm/iosm_ipc_port.c new file mode 100644 index 000000000000..beb944847398 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_port.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include "iosm_ipc_chnl_cfg.h" +#include "iosm_ipc_imem_ops.h" +#include "iosm_ipc_port.h" + +/* open logical channel for control communication */ +static int ipc_port_ctrl_start(struct wwan_port *port) +{ + struct iosm_cdev *ipc_port = wwan_port_get_drvdata(port); + int ret = 0; + + ipc_port->channel = ipc_imem_sys_port_open(ipc_port->ipc_imem, + ipc_port->chl_id, + IPC_HP_CDEV_OPEN); + if (!ipc_port->channel) + ret = -EIO; + + return ret; +} + +/* close logical channel */ +static void ipc_port_ctrl_stop(struct wwan_port *port) +{ + struct iosm_cdev *ipc_port = wwan_port_get_drvdata(port); + + ipc_imem_sys_cdev_close(ipc_port); +} + +/* transfer control data to modem */ +static int ipc_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb) +{ + struct iosm_cdev *ipc_port = wwan_port_get_drvdata(port); + + return ipc_imem_sys_cdev_write(ipc_port, skb); +} + +static const struct wwan_port_ops ipc_wwan_ctrl_ops = { + .start = ipc_port_ctrl_start, + .stop = ipc_port_ctrl_stop, + .tx = ipc_port_ctrl_tx, +}; + +/* Port init func */ +struct iosm_cdev *ipc_port_init(struct iosm_imem *ipc_imem, + struct ipc_chnl_cfg ipc_port_cfg) +{ + struct iosm_cdev *ipc_port = kzalloc(sizeof(*ipc_port), GFP_KERNEL); + enum wwan_port_type port_type = ipc_port_cfg.wwan_port_type; + enum ipc_channel_id chl_id = ipc_port_cfg.id; + + if (!ipc_port) + return NULL; + + ipc_port->dev = ipc_imem->dev; + ipc_port->pcie = ipc_imem->pcie; + + ipc_port->port_type = port_type; + ipc_port->chl_id = chl_id; + ipc_port->ipc_imem = ipc_imem; + + ipc_port->iosm_port = wwan_create_port(ipc_port->dev, port_type, + &ipc_wwan_ctrl_ops, ipc_port); + + return ipc_port; +} + +/* Port deinit func */ +void ipc_port_deinit(struct iosm_cdev *port[]) +{ + struct iosm_cdev *ipc_port; + u8 ctrl_chl_nr; + + for (ctrl_chl_nr = 0; ctrl_chl_nr < IPC_MEM_MAX_CHANNELS; + ctrl_chl_nr++) { + if (port[ctrl_chl_nr]) { + ipc_port = port[ctrl_chl_nr]; + wwan_remove_port(ipc_port->iosm_port); + kfree(ipc_port); + } + } +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_port.h b/drivers/net/wwan/iosm/iosm_ipc_port.h new file mode 100644 index 000000000000..11bc8ed21616 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_port.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_PORT_H +#define IOSM_IPC_PORT_H + +#include + +#include "iosm_ipc_imem_ops.h" + +/** + * struct iosm_cdev - State of the char driver layer. + * @iosm_port: Pointer of type wwan_port + * @ipc_imem: imem instance + * @dev: Pointer to device struct + * @pcie: PCIe component + * @port_type: WWAN port type + * @channel: Channel instance + * @chl_id: Channel Indentifier + */ +struct iosm_cdev { + struct wwan_port *iosm_port; + struct iosm_imem *ipc_imem; + struct device *dev; + struct iosm_pcie *pcie; + enum wwan_port_type port_type; + struct ipc_mem_channel *channel; + enum ipc_channel_id chl_id; +}; + +/** + * ipc_port_init - Allocate IPC port & register to wwan subsystem for AT/MBIM + * communication. + * @ipc_imem: Pointer to iosm_imem structure + * @ipc_port_cfg: IPC Port Config + * + * Returns: 0 on success & NULL on failure + */ +struct iosm_cdev *ipc_port_init(struct iosm_imem *ipc_imem, + struct ipc_chnl_cfg ipc_port_cfg); + +/** + * ipc_port_deinit - Free IPC port & unregister port with wwan subsystem. + * @ipc_port: Array of pointer to the ipc port data-struct + */ +void ipc_port_deinit(struct iosm_cdev *ipc_port[]); + +#endif From patchwork Thu May 20 14:01:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443853 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 657F7C43460 for ; Thu, 20 May 2021 14:09:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4DE5C6109F for ; Thu, 20 May 2021 14:09:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236406AbhETOKU (ORCPT ); Thu, 20 May 2021 10:10:20 -0400 Received: from mga11.intel.com ([192.55.52.93]:5245 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S240141AbhETOIp (ORCPT ); Thu, 20 May 2021 10:08:45 -0400 IronPort-SDR: kZmNl6N9TgyynuC9uhJeOrehDfLPTyBpBrMTWoFTnk7YI+YL71StqMIbV82jujoQ+hFzbZsNBe gOaJ7kt58Y/A== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144639" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144639" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:02:52 -0700 IronPort-SDR: w8M09LeLAxuUjAxW1MblRaMyV1+7oL5CO9aa5aCTmO4D7RCl7QDxmcRBay0mS/TrYUjzaLzX53 rt0XhPJmeDOA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407577" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:02:51 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 09/16] net: iosm: multiplex IP sessions Date: Thu, 20 May 2021 19:31:51 +0530 Message-Id: <20210520140158.10132-10-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Establish IP session between host-device & session management. Signed-off-by: M Chetan Kumar --- v3: Aligned ipc_ prefix for function name to be consistent across file. v2: * Endianness type correction for Host-Device protocol structure. * Removed space around the : for the bitfields. * Change session from dynamic to static. * Streamline multiple returns using goto. --- drivers/net/wwan/iosm/iosm_ipc_mux.c | 455 +++++++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_mux.h | 343 ++++++++++++++++++++ 2 files changed, 798 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_mux.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_mux.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux.c b/drivers/net/wwan/iosm/iosm_ipc_mux.c new file mode 100644 index 000000000000..c1c77ce699da --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_mux.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include "iosm_ipc_mux_codec.h" + +/* At the begin of the runtime phase the IP MUX channel shall created. */ +static int ipc_mux_channel_create(struct iosm_mux *ipc_mux) +{ + int channel_id; + + channel_id = ipc_imem_channel_alloc(ipc_mux->imem, ipc_mux->instance_id, + IPC_CTYPE_WWAN); + + if (channel_id < 0) { + dev_err(ipc_mux->dev, + "allocation of the MUX channel id failed"); + ipc_mux->state = MUX_S_ERROR; + ipc_mux->event = MUX_E_NOT_APPLICABLE; + goto no_channel; + } + + /* Establish the MUX channel in blocking mode. */ + ipc_mux->channel = ipc_imem_channel_open(ipc_mux->imem, channel_id, + IPC_HP_NET_CHANNEL_INIT); + + if (!ipc_mux->channel) { + dev_err(ipc_mux->dev, "ipc_imem_channel_open failed"); + ipc_mux->state = MUX_S_ERROR; + ipc_mux->event = MUX_E_NOT_APPLICABLE; + return -ENODEV; /* MUX channel is not available. */ + } + + /* Define the MUX active state properties. */ + ipc_mux->state = MUX_S_ACTIVE; + ipc_mux->event = MUX_E_NO_ORDERS; + +no_channel: + return channel_id; +} + +/* Reset the session/if id state. */ +static void ipc_mux_session_free(struct iosm_mux *ipc_mux, int if_id) +{ + struct mux_session *if_entry; + + if_entry = &ipc_mux->session[if_id]; + /* Reset the session state. */ + if_entry->wwan = NULL; +} + +/* Create and send the session open command. */ +static struct mux_cmd_open_session_resp * +ipc_mux_session_open_send(struct iosm_mux *ipc_mux, int if_id) +{ + struct mux_cmd_open_session_resp *open_session_resp; + struct mux_acb *acb = &ipc_mux->acb; + union mux_cmd_param param; + + /* open_session commands to one ACB and start transmission. */ + param.open_session.flow_ctrl = 0; + param.open_session.ipv4v6_hints = 0; + param.open_session.reserved2 = 0; + param.open_session.dl_head_pad_len = cpu_to_le32(IPC_MEM_DL_ETH_OFFSET); + + /* Finish and transfer ACB. The user thread is suspended. + * It is a blocking function call, until CP responds or timeout. + */ + acb->wanted_response = MUX_CMD_OPEN_SESSION_RESP; + if (ipc_mux_dl_acb_send_cmds(ipc_mux, MUX_CMD_OPEN_SESSION, if_id, 0, + ¶m, sizeof(param.open_session), true, + false) || + acb->got_response != MUX_CMD_OPEN_SESSION_RESP) { + dev_err(ipc_mux->dev, "if_id %d: OPEN_SESSION send failed", + if_id); + return NULL; + } + + open_session_resp = &ipc_mux->acb.got_param.open_session_resp; + if (open_session_resp->response != cpu_to_le32(MUX_CMD_RESP_SUCCESS)) { + dev_err(ipc_mux->dev, + "if_id %d,session open failed,response=%d", if_id, + open_session_resp->response); + return NULL; + } + + return open_session_resp; +} + +/* Open the first IP session. */ +static bool ipc_mux_session_open(struct iosm_mux *ipc_mux, + struct mux_session_open *session_open) +{ + struct mux_cmd_open_session_resp *open_session_resp; + int if_id; + + /* Search for a free session interface id. */ + if_id = le32_to_cpu(session_open->if_id); + if (if_id < 0 || if_id >= ipc_mux->nr_sessions) { + dev_err(ipc_mux->dev, "invalid interface id=%d", if_id); + return false; + } + + /* Create and send the session open command. + * It is a blocking function call, until CP responds or timeout. + */ + open_session_resp = ipc_mux_session_open_send(ipc_mux, if_id); + if (!open_session_resp) { + ipc_mux_session_free(ipc_mux, if_id); + session_open->if_id = cpu_to_le32(-1); + return false; + } + + /* Initialize the uplink skb accumulator. */ + skb_queue_head_init(&ipc_mux->session[if_id].ul_list); + + ipc_mux->session[if_id].dl_head_pad_len = IPC_MEM_DL_ETH_OFFSET; + ipc_mux->session[if_id].ul_head_pad_len = + le32_to_cpu(open_session_resp->ul_head_pad_len); + ipc_mux->session[if_id].wwan = ipc_mux->wwan; + + /* Reset the flow ctrl stats of the session */ + ipc_mux->session[if_id].flow_ctl_en_cnt = 0; + ipc_mux->session[if_id].flow_ctl_dis_cnt = 0; + ipc_mux->session[if_id].ul_flow_credits = 0; + ipc_mux->session[if_id].net_tx_stop = false; + ipc_mux->session[if_id].flow_ctl_mask = 0; + + /* Save and return the assigned if id. */ + session_open->if_id = cpu_to_le32(if_id); + + return true; +} + +/* Free pending session UL packet. */ +static void ipc_mux_session_reset(struct iosm_mux *ipc_mux, int if_id) +{ + /* Reset the session/if id state. */ + ipc_mux_session_free(ipc_mux, if_id); + + /* Empty the uplink skb accumulator. */ + skb_queue_purge(&ipc_mux->session[if_id].ul_list); +} + +static void ipc_mux_session_close(struct iosm_mux *ipc_mux, + struct mux_session_close *msg) +{ + int if_id; + + /* Copy the session interface id. */ + if_id = le32_to_cpu(msg->if_id); + + if (if_id < 0 || if_id >= ipc_mux->nr_sessions) { + dev_err(ipc_mux->dev, "invalid session id %d", if_id); + return; + } + + /* Create and send the session close command. + * It is a blocking function call, until CP responds or timeout. + */ + if (ipc_mux_dl_acb_send_cmds(ipc_mux, MUX_CMD_CLOSE_SESSION, if_id, 0, + NULL, 0, true, false)) + dev_err(ipc_mux->dev, "if_id %d: CLOSE_SESSION send failed", + if_id); + + /* Reset the flow ctrl stats of the session */ + ipc_mux->session[if_id].flow_ctl_en_cnt = 0; + ipc_mux->session[if_id].flow_ctl_dis_cnt = 0; + ipc_mux->session[if_id].flow_ctl_mask = 0; + + ipc_mux_session_reset(ipc_mux, if_id); +} + +static void ipc_mux_channel_close(struct iosm_mux *ipc_mux, + struct mux_channel_close *channel_close_p) +{ + int i; + + /* Free pending session UL packet. */ + for (i = 0; i < ipc_mux->nr_sessions; i++) + if (ipc_mux->session[i].wwan) + ipc_mux_session_reset(ipc_mux, i); + + ipc_imem_channel_close(ipc_mux->imem, ipc_mux->channel_id); + + /* Reset the MUX object. */ + ipc_mux->state = MUX_S_INACTIVE; + ipc_mux->event = MUX_E_INACTIVE; +} + +/* CP has interrupted AP. If AP is in IP MUX mode, execute the pending ops. */ +static int ipc_mux_schedule(struct iosm_mux *ipc_mux, union mux_msg *msg) +{ + enum mux_event order; + bool success; + int ret = -EIO; + + if (!ipc_mux->initialized) { + ret = -EAGAIN; + goto out; + } + + order = msg->common.event; + + switch (ipc_mux->state) { + case MUX_S_INACTIVE: + if (order != MUX_E_MUX_SESSION_OPEN) + goto out; /* Wait for the request to open a session */ + + if (ipc_mux->event == MUX_E_INACTIVE) + /* Establish the MUX channel and the new state. */ + ipc_mux->channel_id = ipc_mux_channel_create(ipc_mux); + + if (ipc_mux->state != MUX_S_ACTIVE) { + ret = ipc_mux->channel_id; /* Missing the MUX channel */ + goto out; + } + + /* Disable the TD update timer and open the first IP session. */ + ipc_imem_td_update_timer_suspend(ipc_mux->imem, true); + ipc_mux->event = MUX_E_MUX_SESSION_OPEN; + success = ipc_mux_session_open(ipc_mux, &msg->session_open); + + ipc_imem_td_update_timer_suspend(ipc_mux->imem, false); + if (success) + ret = ipc_mux->channel_id; + goto out; + + case MUX_S_ACTIVE: + switch (order) { + case MUX_E_MUX_SESSION_OPEN: + /* Disable the TD update timer and open a session */ + ipc_imem_td_update_timer_suspend(ipc_mux->imem, true); + ipc_mux->event = MUX_E_MUX_SESSION_OPEN; + success = ipc_mux_session_open(ipc_mux, + &msg->session_open); + ipc_imem_td_update_timer_suspend(ipc_mux->imem, false); + if (success) + ret = ipc_mux->channel_id; + goto out; + + case MUX_E_MUX_SESSION_CLOSE: + /* Release an IP session. */ + ipc_mux->event = MUX_E_MUX_SESSION_CLOSE; + ipc_mux_session_close(ipc_mux, &msg->session_close); + ret = ipc_mux->channel_id; + goto out; + + case MUX_E_MUX_CHANNEL_CLOSE: + /* Close the MUX channel pipes. */ + ipc_mux->event = MUX_E_MUX_CHANNEL_CLOSE; + ipc_mux_channel_close(ipc_mux, &msg->channel_close); + ret = ipc_mux->channel_id; + goto out; + + default: + /* Invalid order. */ + goto out; + } + + default: + dev_err(ipc_mux->dev, + "unexpected MUX transition: state=%d, event=%d", + ipc_mux->state, ipc_mux->event); + } +out: + return ret; +} + +struct iosm_mux *ipc_mux_init(struct ipc_mux_config *mux_cfg, + struct iosm_imem *imem) +{ + struct iosm_mux *ipc_mux = kzalloc(sizeof(*ipc_mux), GFP_KERNEL); + int i, ul_tds, ul_td_size; + struct sk_buff_head *free_list; + struct sk_buff *skb; + + if (!ipc_mux) + return NULL; + + ipc_mux->protocol = mux_cfg->protocol; + ipc_mux->ul_flow = mux_cfg->ul_flow; + ipc_mux->nr_sessions = mux_cfg->nr_sessions; + ipc_mux->instance_id = mux_cfg->instance_id; + ipc_mux->wwan_q_offset = 0; + + ipc_mux->pcie = imem->pcie; + ipc_mux->imem = imem; + ipc_mux->ipc_protocol = imem->ipc_protocol; + ipc_mux->dev = imem->dev; + ipc_mux->wwan = imem->wwan; + + /* Get the reference to the UL ADB list. */ + free_list = &ipc_mux->ul_adb.free_list; + + /* Initialize the list with free ADB. */ + skb_queue_head_init(free_list); + + ul_td_size = IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE; + + ul_tds = IPC_MEM_MAX_TDS_MUX_LITE_UL; + + ipc_mux->ul_adb.dest_skb = NULL; + + ipc_mux->initialized = true; + ipc_mux->adb_prep_ongoing = false; + ipc_mux->size_needed = 0; + ipc_mux->ul_data_pend_bytes = 0; + ipc_mux->state = MUX_S_INACTIVE; + ipc_mux->ev_mux_net_transmit_pending = false; + ipc_mux->tx_transaction_id = 0; + ipc_mux->rr_next_session = 0; + ipc_mux->event = MUX_E_INACTIVE; + ipc_mux->channel_id = -1; + ipc_mux->channel = NULL; + + /* Allocate the list of UL ADB. */ + for (i = 0; i < ul_tds; i++) { + dma_addr_t mapping; + + skb = ipc_pcie_alloc_skb(ipc_mux->pcie, ul_td_size, GFP_ATOMIC, + &mapping, DMA_TO_DEVICE, 0); + if (!skb) { + ipc_mux_deinit(ipc_mux); + return NULL; + } + /* Extend the UL ADB list. */ + skb_queue_tail(free_list, skb); + } + + return ipc_mux; +} + +/* Informs the network stack to restart transmission for all opened session if + * Flow Control is not ON for that session. + */ +static void ipc_mux_restart_tx_for_all_sessions(struct iosm_mux *ipc_mux) +{ + struct mux_session *session; + int idx; + + for (idx = 0; idx < ipc_mux->nr_sessions; idx++) { + session = &ipc_mux->session[idx]; + + if (!session->wwan) + continue; + + /* If flow control of the session is OFF and if there was tx + * stop then restart. Inform the network interface to restart + * sending data. + */ + if (session->flow_ctl_mask == 0) { + session->net_tx_stop = false; + ipc_mux_netif_tx_flowctrl(session, idx, false); + } + } +} + +/* Informs the network stack to stop sending further pkt for all opened + * sessions + */ +static void ipc_mux_stop_netif_for_all_sessions(struct iosm_mux *ipc_mux) +{ + struct mux_session *session; + int idx; + + for (idx = 0; idx < ipc_mux->nr_sessions; idx++) { + session = &ipc_mux->session[idx]; + + if (!session->wwan) + continue; + + ipc_mux_netif_tx_flowctrl(session, session->if_id, true); + } +} + +void ipc_mux_check_n_restart_tx(struct iosm_mux *ipc_mux) +{ + if (ipc_mux->ul_flow == MUX_UL) { + int low_thresh = IPC_MEM_MUX_UL_FLOWCTRL_LOW_B; + + if (ipc_mux->ul_data_pend_bytes < low_thresh) + ipc_mux_restart_tx_for_all_sessions(ipc_mux); + } +} + +int ipc_mux_get_max_sessions(struct iosm_mux *ipc_mux) +{ + return ipc_mux ? ipc_mux->nr_sessions : -EFAULT; +} + +enum ipc_mux_protocol ipc_mux_get_active_protocol(struct iosm_mux *ipc_mux) +{ + return ipc_mux ? ipc_mux->protocol : MUX_UNKNOWN; +} + +int ipc_mux_open_session(struct iosm_mux *ipc_mux, int session_nr) +{ + struct mux_session_open *session_open; + union mux_msg mux_msg; + + session_open = &mux_msg.session_open; + session_open->event = MUX_E_MUX_SESSION_OPEN; + + session_open->if_id = cpu_to_le32(session_nr); + ipc_mux->session[session_nr].flags |= IPC_MEM_WWAN_MUX; + return ipc_mux_schedule(ipc_mux, &mux_msg); +} + +int ipc_mux_close_session(struct iosm_mux *ipc_mux, int session_nr) +{ + struct mux_session_close *session_close; + union mux_msg mux_msg; + int ret_val; + + session_close = &mux_msg.session_close; + session_close->event = MUX_E_MUX_SESSION_CLOSE; + + session_close->if_id = cpu_to_le32(session_nr); + ret_val = ipc_mux_schedule(ipc_mux, &mux_msg); + ipc_mux->session[session_nr].flags &= ~IPC_MEM_WWAN_MUX; + + return ret_val; +} + +void ipc_mux_deinit(struct iosm_mux *ipc_mux) +{ + struct mux_channel_close *channel_close; + struct sk_buff_head *free_list; + union mux_msg mux_msg; + struct sk_buff *skb; + + if (!ipc_mux->initialized) + return; + ipc_mux_stop_netif_for_all_sessions(ipc_mux); + + channel_close = &mux_msg.channel_close; + channel_close->event = MUX_E_MUX_CHANNEL_CLOSE; + ipc_mux_schedule(ipc_mux, &mux_msg); + + /* Empty the ADB free list. */ + free_list = &ipc_mux->ul_adb.free_list; + + /* Remove from the head of the downlink queue. */ + while ((skb = skb_dequeue(free_list))) + ipc_pcie_kfree_skb(ipc_mux->pcie, skb); + + if (ipc_mux->channel) { + ipc_mux->channel->ul_pipe.is_open = false; + ipc_mux->channel->dl_pipe.is_open = false; + } + + kfree(ipc_mux); +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux.h b/drivers/net/wwan/iosm/iosm_ipc_mux.h new file mode 100644 index 000000000000..ddd2cd0bd911 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_mux.h @@ -0,0 +1,343 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_MUX_H +#define IOSM_IPC_MUX_H + +#include "iosm_ipc_protocol.h" + +/* Size of the buffer for the IP MUX data buffer. */ +#define IPC_MEM_MAX_DL_MUX_BUF_SIZE (16 * 1024) +#define IPC_MEM_MAX_UL_ADB_BUF_SIZE IPC_MEM_MAX_DL_MUX_BUF_SIZE + +/* Size of the buffer for the IP MUX Lite data buffer. */ +#define IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE (2 * 1024) + +/* TD counts for IP MUX Lite */ +#define IPC_MEM_MAX_TDS_MUX_LITE_UL 800 +#define IPC_MEM_MAX_TDS_MUX_LITE_DL 1200 + +/* open session request (AP->CP) */ +#define MUX_CMD_OPEN_SESSION 1 + +/* response to open session request (CP->AP) */ +#define MUX_CMD_OPEN_SESSION_RESP 2 + +/* close session request (AP->CP) */ +#define MUX_CMD_CLOSE_SESSION 3 + +/* response to close session request (CP->AP) */ +#define MUX_CMD_CLOSE_SESSION_RESP 4 + +/* Flow control command with mask of the flow per queue/flow. */ +#define MUX_LITE_CMD_FLOW_CTL 5 + +/* ACK the flow control command. Shall have the same Transaction ID as the + * matching FLOW_CTL command. + */ +#define MUX_LITE_CMD_FLOW_CTL_ACK 6 + +/* Command for report packet indicating link quality metrics. */ +#define MUX_LITE_CMD_LINK_STATUS_REPORT 7 + +/* Response to a report packet */ +#define MUX_LITE_CMD_LINK_STATUS_REPORT_RESP 8 + +/* Used to reset a command/response state. */ +#define MUX_CMD_INVALID 255 + +/* command response : command processed successfully */ +#define MUX_CMD_RESP_SUCCESS 0 + +/* MUX for route link devices */ +#define IPC_MEM_WWAN_MUX BIT(0) + +/* Initiated actions to change the state of the MUX object. */ +enum mux_event { + MUX_E_INACTIVE, /* No initiated actions. */ + MUX_E_MUX_SESSION_OPEN, /* Create the MUX channel and a session. */ + MUX_E_MUX_SESSION_CLOSE, /* Release a session. */ + MUX_E_MUX_CHANNEL_CLOSE, /* Release the MUX channel. */ + MUX_E_NO_ORDERS, /* No MUX order. */ + MUX_E_NOT_APPLICABLE, /* Defect IP MUX. */ +}; + +/* MUX session open command. */ +struct mux_session_open { + enum mux_event event; + __le32 if_id; +}; + +/* MUX session close command. */ +struct mux_session_close { + enum mux_event event; + __le32 if_id; +}; + +/* MUX channel close command. */ +struct mux_channel_close { + enum mux_event event; +}; + +/* Default message type to find out the right message type. */ +struct mux_common { + enum mux_event event; +}; + +/* List of ops in MUX mode. */ +union mux_msg { + struct mux_session_open session_open; + struct mux_session_close session_close; + struct mux_channel_close channel_close; + struct mux_common common; +}; + +/* Parameter definition of the open session command. */ +struct mux_cmd_open_session { + u8 flow_ctrl; /* 0: Flow control disabled (flow allowed). */ + /* 1: Flow control enabled (flow not allowed)*/ + u8 ipv4v6_hints; /* 0: IPv4/IPv6 hints not supported.*/ + /* 1: IPv4/IPv6 hints supported*/ + __le16 reserved2; /* Reserved. Set to zero. */ + __le32 dl_head_pad_len; /* Maximum length supported */ + /* for DL head padding on a datagram. */ +}; + +/* Parameter definition of the open session response. */ +struct mux_cmd_open_session_resp { + __le32 response; /* Response code */ + u8 flow_ctrl; /* 0: Flow control disabled (flow allowed). */ + /* 1: Flow control enabled (flow not allowed) */ + u8 ipv4v6_hints; /* 0: IPv4/IPv6 hints not supported */ + /* 1: IPv4/IPv6 hints supported */ + __le16 reserved2; /* Reserved. Set to zero. */ + __le32 ul_head_pad_len; /* Actual length supported for */ + /* UL head padding on adatagram.*/ +}; + +/* Parameter definition of the close session response code */ +struct mux_cmd_close_session_resp { + __le32 response; +}; + +/* Parameter definition of the flow control command. */ +struct mux_cmd_flow_ctl { + __le32 mask; /* indicating the desired flow control */ + /* state for various flows/queues */ +}; + +/* Parameter definition of the link status report code*/ +struct mux_cmd_link_status_report { + u8 payload; +}; + +/* Parameter definition of the link status report response code. */ +struct mux_cmd_link_status_report_resp { + __le32 response; +}; + +/** + * union mux_cmd_param - Union-definition of the command parameters. + * @open_session: Inband command for open session + * @open_session_resp: Inband command for open session response + * @close_session_resp: Inband command for close session response + * @flow_ctl: In-band flow control on the opened interfaces + * @link_status: In-band Link Status Report + * @link_status_resp: In-band command for link status report response + */ +union mux_cmd_param { + struct mux_cmd_open_session open_session; + struct mux_cmd_open_session_resp open_session_resp; + struct mux_cmd_close_session_resp close_session_resp; + struct mux_cmd_flow_ctl flow_ctl; + struct mux_cmd_link_status_report link_status; + struct mux_cmd_link_status_report_resp link_status_resp; +}; + +/* States of the MUX object.. */ +enum mux_state { + MUX_S_INACTIVE, /* IP MUX is unused. */ + MUX_S_ACTIVE, /* IP MUX channel is available. */ + MUX_S_ERROR, /* Defect IP MUX. */ +}; + +/* Supported MUX protocols. */ +enum ipc_mux_protocol { + MUX_UNKNOWN, + MUX_LITE, +}; + +/* Supported UL data transfer methods. */ +enum ipc_mux_ul_flow { + MUX_UL_UNKNOWN, + MUX_UL, /* Normal UL data transfer */ + MUX_UL_ON_CREDITS, /* UL data transfer will be based on credits */ +}; + +/* List of the MUX session. */ +struct mux_session { + struct iosm_wwan *wwan; /*Network i/f used for communication*/ + int if_id; /* i/f id for session open message.*/ + u32 flags; + u32 ul_head_pad_len; /* Nr of bytes for UL head padding. */ + u32 dl_head_pad_len; /* Nr of bytes for DL head padding. */ + struct sk_buff_head ul_list; /* skb entries for an ADT. */ + u32 flow_ctl_mask; /* UL flow control */ + u32 flow_ctl_en_cnt; /* Flow control Enable cmd count */ + u32 flow_ctl_dis_cnt; /* Flow Control Disable cmd count */ + int ul_flow_credits; /* UL flow credits */ + u8 net_tx_stop:1, + flush:1; /* flush net interface ? */ +}; + +/* State of a single UL data block. */ +struct mux_adb { + struct sk_buff *dest_skb; /* Current UL skb for the data block. */ + u8 *buf; /* ADB memory. */ + struct mux_adgh *adgh; /* ADGH pointer */ + struct sk_buff *qlth_skb; /* QLTH pointer */ + u32 *next_table_index; /* Pointer to next table index. */ + struct sk_buff_head free_list; /* List of alloc. ADB for the UL sess.*/ + int size; /* Size of the ADB memory. */ + u32 if_cnt; /* Statistic counter */ + u32 dg_cnt_total; + u32 payload_size; +}; + +/* Temporary ACB state. */ +struct mux_acb { + struct sk_buff *skb; /* Used UL skb. */ + int if_id; /* Session id. */ + u32 wanted_response; + u32 got_response; + u32 cmd; + union mux_cmd_param got_param; /* Received command/response parameter */ +}; + +/** + * struct iosm_mux - Structure of the data multiplexing over an IP channel. + * @dev: Pointer to device structure + * @session: Array of the MUX sessions. + * @channel: Reference to the IP MUX channel + * @pcie: Pointer to iosm_pcie struct + * @imem: Pointer to iosm_imem + * @wwan: Poinetr to iosm_wwan + * @ipc_protocol: Pointer to iosm_protocol + * @channel_id: Channel ID for MUX + * @protocol: Type of the MUX protocol + * @ul_flow: UL Flow type + * @nr_sessions: Number of sessions + * @instance_id: Instance ID + * @state: States of the MUX object + * @event: Initiated actions to change the state of the MUX object + * @tx_transaction_id: Transaction id for the ACB command. + * @rr_next_session: Next session number for round robin. + * @ul_adb: State of the UL ADB/ADGH. + * @size_needed: Variable to store the size needed during ADB preparation + * @ul_data_pend_bytes: Pending UL data to be processed in bytes + * @acb: Temporary ACB state + * @wwan_q_offset: This will hold the offset of the given instance + * Useful while passing or receiving packets from + * wwan/imem layer. + * @initialized: MUX object is initialized + * @ev_mux_net_transmit_pending: + * 0 means inform the IPC tasklet to pass the + * accumulated uplink ADB to CP. + * @adb_prep_ongoing: Flag for ADB preparation status + */ +struct iosm_mux { + struct device *dev; + struct mux_session session[IPC_MEM_MUX_IP_SESSION_ENTRIES]; + struct ipc_mem_channel *channel; + struct iosm_pcie *pcie; + struct iosm_imem *imem; + struct iosm_wwan *wwan; + struct iosm_protocol *ipc_protocol; + int channel_id; + enum ipc_mux_protocol protocol; + enum ipc_mux_ul_flow ul_flow; + int nr_sessions; + int instance_id; + enum mux_state state; + enum mux_event event; + u32 tx_transaction_id; + int rr_next_session; + struct mux_adb ul_adb; + int size_needed; + long long ul_data_pend_bytes; + struct mux_acb acb; + int wwan_q_offset; + u8 initialized:1, + ev_mux_net_transmit_pending:1, + adb_prep_ongoing:1; +}; + +/* MUX configuration structure */ +struct ipc_mux_config { + enum ipc_mux_protocol protocol; + enum ipc_mux_ul_flow ul_flow; + int nr_sessions; + int instance_id; +}; + +/** + * ipc_mux_init - Allocates and Init MUX instance + * @mux_cfg: Pointer to MUX configuration structure + * @ipc_imem: Pointer to imem data-struct + * + * Returns: Initialized mux pointer on success else NULL + */ +struct iosm_mux *ipc_mux_init(struct ipc_mux_config *mux_cfg, + struct iosm_imem *ipc_imem); + +/** + * ipc_mux_deinit - Deallocates MUX instance + * @ipc_mux: Pointer to the MUX instance. + */ +void ipc_mux_deinit(struct iosm_mux *ipc_mux); + +/** + * ipc_mux_check_n_restart_tx - Checks for pending UL date bytes and then + * it restarts the net interface tx queue if + * device has set flow control as off. + * @ipc_mux: Pointer to MUX data-struct + */ +void ipc_mux_check_n_restart_tx(struct iosm_mux *ipc_mux); + +/** + * ipc_mux_get_active_protocol - Returns the active MUX protocol type. + * @ipc_mux: Pointer to MUX data-struct + * + * Returns: enum of type ipc_mux_protocol + */ +enum ipc_mux_protocol ipc_mux_get_active_protocol(struct iosm_mux *ipc_mux); + +/** + * ipc_mux_open_session - Opens a MUX session for IP traffic. + * @ipc_mux: Pointer to MUX data-struct + * @session_nr: Interface ID or session number + * + * Returns: channel id on success, failure value on error + */ +int ipc_mux_open_session(struct iosm_mux *ipc_mux, int session_nr); + +/** + * ipc_mux_close_session - Closes a MUX session. + * @ipc_mux: Pointer to MUX data-struct + * @session_nr: Interface ID or session number + * + * Returns: channel id on success, failure value on error + */ +int ipc_mux_close_session(struct iosm_mux *ipc_mux, int session_nr); + +/** + * ipc_mux_get_max_sessions - Retuns the maximum sessions supported on the + * provided MUX instance.. + * @ipc_mux: Pointer to MUX data-struct + * + * Returns: Number of sessions supported on Success and failure value on error + */ +int ipc_mux_get_max_sessions(struct iosm_mux *ipc_mux); +#endif From patchwork Thu May 20 14:01:56 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443852 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 57136C43460 for ; Thu, 20 May 2021 14:09:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3C51B611AB for ; Thu, 20 May 2021 14:09:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243713AbhETOKs (ORCPT ); Thu, 20 May 2021 10:10:48 -0400 Received: from mga11.intel.com ([192.55.52.93]:5047 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S243757AbhETOJ2 (ORCPT ); Thu, 20 May 2021 10:09:28 -0400 IronPort-SDR: Gyj4mawADLZNprhqTfDFXNTEoVi6iHUzqYcuibaBzlG3ZNNKWvOoGyVa3j/EAO87KC5WstJUkr rHQ+fjWCIdog== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144793" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144793" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:03:07 -0700 IronPort-SDR: wkfWrmzoOmnWUuN1tqsH5xsRwZ9dfXE7MMlizUqm7SyCXjAQD0/g/kbNkOn1GHdfM6IuCSk4Sa M0lNqWI8+dwg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407654" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:03:05 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 14/16] net: iosm: uevent support Date: Thu, 20 May 2021 19:31:56 +0530 Message-Id: <20210520140158.10132-15-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Report modem status via uevent. Signed-off-by: M Chetan Kumar --- v3: no change. v2: Removed non-related header file inclusion. --- drivers/net/wwan/iosm/iosm_ipc_uevent.c | 44 +++++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_uevent.h | 41 +++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_uevent.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_uevent.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_uevent.c b/drivers/net/wwan/iosm/iosm_ipc_uevent.c new file mode 100644 index 000000000000..2229d752926c --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_uevent.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include +#include +#include + +#include "iosm_ipc_uevent.h" + +/* Update the uevent in work queue context */ +static void ipc_uevent_work(struct work_struct *data) +{ + struct ipc_uevent_info *info; + char *envp[2] = { NULL, NULL }; + + info = container_of(data, struct ipc_uevent_info, work); + + envp[0] = info->uevent; + + if (kobject_uevent_env(&info->dev->kobj, KOBJ_CHANGE, envp)) + pr_err("uevent %s failed to sent", info->uevent); + + kfree(info); +} + +void ipc_uevent_send(struct device *dev, char *uevent) +{ + struct ipc_uevent_info *info = kzalloc(sizeof(*info), GFP_ATOMIC); + + if (!info) + return; + + /* Initialize the kernel work queue */ + INIT_WORK(&info->work, ipc_uevent_work); + + /* Store the device and event information */ + info->dev = dev; + snprintf(info->uevent, MAX_UEVENT_LEN, "%s: %s", dev_name(dev), uevent); + + /* Schedule uevent in process context using work queue */ + schedule_work(&info->work); +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_uevent.h b/drivers/net/wwan/iosm/iosm_ipc_uevent.h new file mode 100644 index 000000000000..2e45c051b5f4 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_uevent.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_UEVENT_H +#define IOSM_IPC_UEVENT_H + +/* Baseband event strings */ +#define UEVENT_MDM_NOT_READY "MDM_NOT_READY" +#define UEVENT_ROM_READY "ROM_READY" +#define UEVENT_MDM_READY "MDM_READY" +#define UEVENT_CRASH "CRASH" +#define UEVENT_CD_READY "CD_READY" +#define UEVENT_CD_READY_LINK_DOWN "CD_READY_LINK_DOWN" +#define UEVENT_MDM_TIMEOUT "MDM_TIMEOUT" + +/* Maximum length of user events */ +#define MAX_UEVENT_LEN 64 + +/** + * struct ipc_uevent_info - Uevent information structure. + * @dev: Pointer to device structure + * @uevent: Uevent information + * @work: Uevent work struct + */ +struct ipc_uevent_info { + struct device *dev; + char uevent[MAX_UEVENT_LEN]; + struct work_struct work; +}; + +/** + * ipc_uevent_send - Send modem event to user space. + * @dev: Generic device pointer + * @uevent: Uevent information + * + */ +void ipc_uevent_send(struct device *dev, char *uevent); + +#endif From patchwork Thu May 20 14:01:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kumar, M Chetan" X-Patchwork-Id: 443851 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A41F3C43460 for ; Thu, 20 May 2021 14:09:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 88FB4611AB for ; Thu, 20 May 2021 14:09:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243741AbhETOK4 (ORCPT ); Thu, 20 May 2021 10:10:56 -0400 Received: from mga11.intel.com ([192.55.52.93]:5246 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S243926AbhETOKC (ORCPT ); Thu, 20 May 2021 10:10:02 -0400 IronPort-SDR: iyhuqSiWrGEpo2vxIsdP2MS9fAsnJaWvRlexLe0qT4UOJwFmk7pfhuKaNLTcXTdzTvoxY0pSkP wUa+5wyleheQ== X-IronPort-AV: E=McAfee;i="6200,9189,9989"; a="198144825" X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="198144825" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 20 May 2021 07:03:10 -0700 IronPort-SDR: CyWZP97Z3jMcMYIsTqFL0vPnDFUBsWINE/UEfdIuP8DRNjyzgz3XXf2St0YMkSFeOrWaQmIni/ Wk94ZWU98SVA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.82,313,1613462400"; d="scan'208";a="631407670" Received: from bgsxx0031.iind.intel.com ([10.106.222.40]) by fmsmga005.fm.intel.com with ESMTP; 20 May 2021 07:03:08 -0700 From: M Chetan Kumar To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org Cc: johannes@sipsolutions.net, krishna.c.sudi@intel.com, linuxwwan@intel.com Subject: [PATCH V3 15/16] net: iosm: net driver Date: Thu, 20 May 2021 19:31:57 +0530 Message-Id: <20210520140158.10132-16-m.chetan.kumar@intel.com> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20210520140158.10132-1-m.chetan.kumar@intel.com> References: <20210520140158.10132-1-m.chetan.kumar@intel.com> Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 1) Create net device & implement net operations for data/IP communication. 2) Bind IP Link to mux IP session for simultaneous IP traffic. Signed-off-by: M Chetan Kumar --- v3: * Clean-up DSS channel implementation. * Aligned ipc_ prefix for function name to be consistent across file. v2: * Removed Ethernet header & VLAN tag handling from wwan net driver. * Implement rtnet_link interface for IP traffic handling. --- drivers/net/wwan/iosm/iosm_ipc_wwan.c | 439 ++++++++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_wwan.h | 55 ++++ 2 files changed, 494 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_wwan.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_wwan.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_wwan.c b/drivers/net/wwan/iosm/iosm_ipc_wwan.c new file mode 100644 index 000000000000..02c35bc86674 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_wwan.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include +#include +#include +#include + +#include "iosm_ipc_chnl_cfg.h" +#include "iosm_ipc_imem_ops.h" +#include "iosm_ipc_wwan.h" + +#define IOSM_IP_TYPE_MASK 0xF0 +#define IOSM_IP_TYPE_IPV4 0x40 +#define IOSM_IP_TYPE_IPV6 0x60 + +#define IOSM_IF_ID_PAYLOAD 2 + +static struct device_type wwan_type = { .name = "wwan" }; + +static const struct nla_policy ipc_wwan_policy[IFLA_IOSM_MAX + 1] = { + [IFLA_IOSM_IF_ID] = { .type = NLA_U16 }, +}; + +/** + * struct iosm_net_link - This structure includes information about interface + * dev. + * @if_id: Interface id for device. + * @ch_id: IPC channel number for which interface device is created. + * @netdev: Pointer to network interface device structure + * @ipc_wwan: Pointer to iosm_wwan struct + */ + +struct iosm_net_link { + int if_id; + int ch_id; + struct net_device *netdev; + struct iosm_wwan *ipc_wwan; +}; + +/** + * struct iosm_wwan - This structure contains information about WWAN root device + * and interface to the IPC layer. + * @netdev: Pointer to network interface device structure. + * @sub_netlist: List of netlink interfaces + * @ipc_imem: Pointer to imem data-struct + * @dev: Pointer device structure + * @if_mutex: Mutex used for add and remove interface id + * @is_registered: Registration status with netdev + */ +struct iosm_wwan { + struct net_device *netdev; + struct iosm_net_link __rcu *sub_netlist[MAX_IP_MUX_SESSION]; + struct iosm_imem *ipc_imem; + struct device *dev; + struct mutex if_mutex; /* Mutex used for add and remove interface id */ + u8 is_registered:1; +}; + +/* Bring-up the wwan net link */ +static int ipc_wwan_link_open(struct net_device *netdev) +{ + struct iosm_net_link *netlink = netdev_priv(netdev); + struct iosm_wwan *ipc_wwan = netlink->ipc_wwan; + int if_id = netlink->if_id; + int ret = -EINVAL; + + if (if_id < IP_MUX_SESSION_START || if_id > IP_MUX_SESSION_END) + return ret; + + mutex_lock(&ipc_wwan->if_mutex); + + /* get channel id */ + netlink->ch_id = + ipc_imem_sys_wwan_open(ipc_wwan->ipc_imem, if_id); + + if (netlink->ch_id < 0) { + dev_err(ipc_wwan->dev, + "cannot connect wwan0 & id %d to the IPC mem layer", + if_id); + ret = -ENODEV; + goto out; + } + + /* enable tx path, DL data may follow */ + netif_start_queue(netdev); + + dev_dbg(ipc_wwan->dev, "Channel id %d allocated to if_id %d", + netlink->ch_id, netlink->if_id); + + ret = 0; +out: + mutex_unlock(&ipc_wwan->if_mutex); + return ret; +} + +/* Bring-down the wwan net link */ +static int ipc_wwan_link_stop(struct net_device *netdev) +{ + struct iosm_net_link *netlink = netdev_priv(netdev); + + netif_stop_queue(netdev); + + mutex_lock(&netlink->ipc_wwan->if_mutex); + ipc_imem_sys_wwan_close(netlink->ipc_wwan->ipc_imem, netlink->if_id, + netlink->ch_id); + mutex_unlock(&netlink->ipc_wwan->if_mutex); + + return 0; +} + +/* Transmit a packet */ +static int ipc_wwan_link_transmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct iosm_net_link *netlink = netdev_priv(netdev); + struct iosm_wwan *ipc_wwan = netlink->ipc_wwan; + int if_id = netlink->if_id; + int ret = -EINVAL; + + /* Interface IDs from 1 to 8 are for IP data + * & from 257 to 261 are for non-IP data + */ + if (if_id < IP_MUX_SESSION_START || if_id > IP_MUX_SESSION_END) + goto exit; + + /* Send the SKB to device for transmission */ + ret = ipc_imem_sys_wwan_transmit(ipc_wwan->ipc_imem, + if_id, netlink->ch_id, skb); + + /* Return code of zero is success */ + if (ret == 0) { + ret = NETDEV_TX_OK; + } else if (ret == -EBUSY) { + ret = NETDEV_TX_BUSY; + dev_err(ipc_wwan->dev, "unable to push packets"); + } else { + goto exit; + } + + return ret; + +exit: + /* Log any skb drop */ + if (if_id) + dev_dbg(ipc_wwan->dev, "skb dropped. IF_ID: %d, ret: %d", if_id, + ret); + + dev_kfree_skb_any(skb); + return ret; +} + +/* Ops structure for wwan net link */ +static const struct net_device_ops ipc_inm_ops = { + .ndo_open = ipc_wwan_link_open, + .ndo_stop = ipc_wwan_link_stop, + .ndo_start_xmit = ipc_wwan_link_transmit, +}; + +/* Setup function for creating new net link */ +static void ipc_wwan_setup(struct net_device *iosm_dev) +{ + iosm_dev->header_ops = NULL; + iosm_dev->hard_header_len = 0; + iosm_dev->priv_flags |= IFF_NO_QUEUE; + + iosm_dev->type = ARPHRD_NONE; + iosm_dev->min_mtu = ETH_MIN_MTU; + iosm_dev->max_mtu = ETH_MAX_MTU; + + iosm_dev->flags = IFF_POINTOPOINT | IFF_NOARP; + + iosm_dev->netdev_ops = &ipc_inm_ops; +} + +static struct device_type inm_type = { .name = "iosmdev" }; + +/* Create new wwan net link */ +static int ipc_wwan_newlink(struct net *src_net, struct net_device *dev, + struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + struct iosm_net_link *netlink = netdev_priv(dev); + struct iosm_wwan *ipc_wwan; + struct net_device *netdev; + int err = -EINVAL; + int index; + + if (!data[IFLA_IOSM_IF_ID]) { + pr_err("Interface ID not specified"); + goto out; + } + + if (!tb[IFLA_LINK]) { + pr_err("Link not specified"); + goto out; + } + + netlink->netdev = dev; + + netdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK])); + + netlink->ipc_wwan = netdev_priv(netdev); + + ipc_wwan = netlink->ipc_wwan; + + if (ipc_wwan->netdev != netdev) + goto out; + + netlink->if_id = nla_get_u16(data[IFLA_IOSM_IF_ID]); + index = netlink->if_id; + + /* Complete all memory stores before this point */ + smp_mb(); + if (index < IP_MUX_SESSION_START || index > IP_MUX_SESSION_END) + goto out; + + rcu_read_lock(); + + if (rcu_access_pointer(ipc_wwan->sub_netlist[index - 1])) { + pr_err("IOSM interface ID already in use"); + goto out_free_lock; + } + + SET_NETDEV_DEVTYPE(dev, &inm_type); + + eth_hw_addr_random(dev); + err = register_netdevice(dev); + if (err) { + dev_err(ipc_wwan->dev, "register netlink failed.\n"); + goto out_free_lock; + } + + err = netdev_upper_dev_link(ipc_wwan->netdev, dev, extack); + + if (err) { + dev_err(ipc_wwan->dev, "netdev linking with parent failed.\n"); + goto netlink_err; + } + + rcu_assign_pointer(ipc_wwan->sub_netlist[index - 1], netlink); + netif_device_attach(dev); + rcu_read_unlock(); + + return 0; + +netlink_err: + unregister_netdevice(dev); +out_free_lock: + rcu_read_unlock(); +out: + return err; +} + +/* Delete new wwan net link */ +static void ipc_wwan_dellink(struct net_device *dev, struct list_head *head) +{ + struct iosm_net_link *netlink = netdev_priv(dev); + u16 index = netlink->if_id; + + netdev_upper_dev_unlink(netlink->ipc_wwan->netdev, dev); + unregister_netdevice(dev); + + mutex_lock(&netlink->ipc_wwan->if_mutex); + rcu_assign_pointer(netlink->ipc_wwan->sub_netlist[index - 1], NULL); + mutex_unlock(&netlink->ipc_wwan->if_mutex); +} + +/* Get size for iosm net link payload*/ +static size_t ipc_wwan_get_size(const struct net_device *dev) +{ + return nla_total_size(IOSM_IF_ID_PAYLOAD); +} + +/* Validate the input parameters for wwan net link */ +static int ipc_wwan_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + u16 if_id; + + if (!data || !data[IFLA_IOSM_IF_ID]) { + NL_SET_ERR_MSG_MOD(extack, "IF ID not specified"); + return -EINVAL; + } + + if_id = nla_get_u16(data[IFLA_IOSM_IF_ID]); + + if (if_id < IP_MUX_SESSION_START || if_id > IP_MUX_SESSION_END) { + NL_SET_ERR_MSG_MOD(extack, "Invalid Interface"); + return -ERANGE; + } + + return 0; +} + +/* RT Net link ops structure for new wwan net link */ +struct rtnl_link_ops iosm_netlink __read_mostly = { + .kind = "iosm", + .maxtype = __IFLA_IOSM_MAX, + .priv_size = sizeof(struct iosm_net_link), + .setup = ipc_wwan_setup, + .validate = ipc_wwan_validate, + .newlink = ipc_wwan_newlink, + .dellink = ipc_wwan_dellink, + .get_size = ipc_wwan_get_size, + .policy = ipc_wwan_policy, +}; + +int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg, + bool dss, int if_id) +{ + struct sk_buff *skb = skb_arg; + struct net_device_stats stats; + int ret; + + if ((skb->data[0] & IOSM_IP_TYPE_MASK) == IOSM_IP_TYPE_IPV4) + skb->protocol = htons(ETH_P_IP); + else if ((skb->data[0] & IOSM_IP_TYPE_MASK) == + IOSM_IP_TYPE_IPV6) + skb->protocol = htons(ETH_P_IPV6); + + skb->pkt_type = PACKET_HOST; + + if (if_id < (IP_MUX_SESSION_START - 1) || + if_id > (IP_MUX_SESSION_END - 1)) { + dev_kfree_skb(skb); + return -EINVAL; + } + + rcu_read_lock(); + skb->dev = rcu_dereference(ipc_wwan->sub_netlist[if_id])->netdev; + stats = rcu_dereference(ipc_wwan->sub_netlist[if_id])->netdev->stats; + stats.rx_packets++; + stats.rx_bytes += skb->len; + + ret = netif_rx(skb); + rcu_read_unlock(); + + return ret; +} + +void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int if_id, bool on) +{ + struct net_device *netdev; + bool is_tx_blk; + + rcu_read_lock(); + netdev = rcu_dereference(ipc_wwan->sub_netlist[if_id])->netdev; + + is_tx_blk = netif_queue_stopped(netdev); + + if (on) + dev_dbg(ipc_wwan->dev, "session id[%d]: flowctrl enable", + if_id); + + if (on && !is_tx_blk) + netif_stop_queue(netdev); + else if (!on && is_tx_blk) + netif_wake_queue(netdev); + rcu_read_unlock(); +} + +static void ipc_netdev_setup(struct net_device *dev) {} + +struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev) +{ + static const struct net_device_ops iosm_wwandev_ops = {}; + struct iosm_wwan *ipc_wwan; + struct net_device *netdev; + + netdev = alloc_netdev(sizeof(*ipc_wwan), "wwan%d", NET_NAME_ENUM, + ipc_netdev_setup); + + if (!netdev) + return NULL; + + ipc_wwan = netdev_priv(netdev); + + ipc_wwan->dev = dev; + ipc_wwan->netdev = netdev; + ipc_wwan->is_registered = false; + + ipc_wwan->ipc_imem = ipc_imem; + + mutex_init(&ipc_wwan->if_mutex); + + /* allocate random ethernet address */ + eth_random_addr(netdev->dev_addr); + netdev->addr_assign_type = NET_ADDR_RANDOM; + + netdev->netdev_ops = &iosm_wwandev_ops; + netdev->flags |= IFF_NOARP; + + SET_NETDEV_DEVTYPE(netdev, &wwan_type); + + if (register_netdev(netdev)) { + dev_err(ipc_wwan->dev, "register_netdev failed"); + goto reg_fail; + } + + ipc_wwan->is_registered = true; + + netif_device_attach(netdev); + + return ipc_wwan; + +reg_fail: + free_netdev(netdev); + return NULL; +} + +void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan) +{ + struct iosm_net_link *netlink; + int i; + + if (ipc_wwan->is_registered) { + rcu_read_lock(); + for (i = IP_MUX_SESSION_START; i <= IP_MUX_SESSION_END; i++) { + if (rcu_access_pointer(ipc_wwan->sub_netlist[i - 1])) { + netlink = + rcu_dereference(ipc_wwan->sub_netlist[i - 1]); + rtnl_lock(); + netdev_upper_dev_unlink(ipc_wwan->netdev, + netlink->netdev); + unregister_netdevice(netlink->netdev); + rtnl_unlock(); + rcu_assign_pointer(ipc_wwan->sub_netlist[i - 1], + NULL); + } + } + rcu_read_unlock(); + + unregister_netdev(ipc_wwan->netdev); + free_netdev(ipc_wwan->netdev); + } +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_wwan.h b/drivers/net/wwan/iosm/iosm_ipc_wwan.h new file mode 100644 index 000000000000..4925f22dff0a --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_wwan.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_WWAN_H +#define IOSM_IPC_WWAN_H + +/** + * ipc_wwan_init - Allocate, Init and register WWAN device + * @ipc_imem: Pointer to imem data-struct + * @dev: Pointer to device structure + * + * Returns: Pointer to instance on success else NULL + */ +struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev); + +/** + * ipc_wwan_deinit - Unregister and free WWAN device, clear pointer + * @ipc_wwan: Pointer to wwan instance data + */ +void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan); + +/** + * ipc_wwan_receive - Receive a downlink packet from CP. + * @ipc_wwan: Pointer to wwan instance + * @skb_arg: Pointer to struct sk_buff + * @dss: Set to true if interafce id is from 257 to 261, + * else false + * @if_id: Interface ID + * + * Return: 0 on success and failure value on error + */ +int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg, + bool dss, int if_id); + +/** + * ipc_wwan_tx_flowctrl - Enable/Disable TX flow control + * @ipc_wwan: Pointer to wwan instance + * @id: Ipc mux channel session id + * @on: if true then flow ctrl would be enabled else disable + * + */ +void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int id, bool on); + +/** + * ipc_wwan_is_tx_stopped - Checks if Tx stopped for a Interface id. + * @ipc_wwan: Pointer to wwan instance + * @id: Ipc mux channel session id + * + * Return: true if stopped, false otherwise + */ +bool ipc_wwan_is_tx_stopped(struct iosm_wwan *ipc_wwan, int id); + +#endif