Loading drivers/platform/msm/Kconfig +19 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,25 @@ config IPA_WDI_UNIFIED_API The IPA WDI unified API supports all WDI versions through a unified interface. config IPA_ETH bool "IPA Ethernet Offload Sub-system support" depends on IPA3 help Enables IPA Ethernet Offload Subsystem for offloading PCI based ethernet devices to IPA. The offload subsystem still require a compatible network driver to register with it and a corresponding offload driver to manage one of more offload data paths that uses the network device. config IPA_ETH_NOAUTO bool "Disable automatic offload initialization of interfaces" depends on IPA_ETH help Enabling this option prevents automatic initialization of offload on ethernet interfaces. Debugfs control interface will instead be used to enable offloading. This feature is meant only for debugging. If unsure, say N. config RMNET_IPA3 tristate "IPA3 RMNET WWAN Network Device" depends on IPA3 && QCOM_QMI_HELPERS Loading drivers/platform/msm/ipa/ipa_v3/Makefile +2 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,8 @@ obj-$(CONFIG_RMNET_IPA3) += rmnet_ipa.o ipa_qmi_service_v01.o ipa_qmi_service.o obj-$(CONFIG_IPA3_MHI_PROXY) += ipa_mhi_proxy.o obj-$(CONFIG_IPA_ETH) += ethernet/ ipat-$(CONFIG_IPA3_REGDUMP) += dump/ipa_reg_dump.o ccflags-$(CONFIG_IPA3_REGDUMP_SM8150) += -Idrivers/platform/msm/ipa/ipa_v3/dump/sm8150 drivers/platform/msm/ipa/ipa_v3/ethernet/Makefile 0 → 100644 +12 −0 Original line number Diff line number Diff line obj-$(CONFIG_IPA_ETH) += ipa-eth.o ipa-eth-y := \ ipa_eth_bus.o \ ipa_eth.o \ ipa_eth_ep.o \ ipa_eth_gsi.o \ ipa_eth_offload.o \ ipa_eth_pci.o \ ipa_eth_pm.o \ ipa_eth_uc.o drivers/platform/msm/ipa/ipa_v3/ethernet/ipa_eth.c 0 → 100644 +902 −0 Original line number Diff line number Diff line /* Copyright (c) 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/printk.h> #include <linux/debugfs.h> #include <linux/module.h> #include <linux/msm_ipa.h> #include "ipa_eth_i.h" static bool ipa_eth_is_ready; static bool ipa_eth_ipa_is_ready; static bool ipa_eth_ipa_uc_is_ready; static struct dentry *ipa_eth_debugfs; static struct dentry *ipa_eth_drivers_debugfs; static struct dentry *ipa_eth_devices_debugfs; static LIST_HEAD(ipa_eth_devices); static DEFINE_MUTEX(ipa_eth_devices_lock); static bool ipa_eth_noauto = IPA_ETH_NOAUTO_DEFAULT; module_param(ipa_eth_noauto, bool, 0644); MODULE_PARM_DESC(ipa_eth_noauto, "Disable automatic offload initialization of interfaces"); static inline bool ipa_eth_ready(void) { return ipa_eth_is_ready && ipa_eth_ipa_is_ready && ipa_eth_ipa_uc_is_ready; } static int ipa_eth_init_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_INITED) return 0; if (eth_dev->state != IPA_ETH_ST_DEINITED) return -EFAULT; rc = ipa_eth_ep_init_headers(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to init EP headers"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_pm_register(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to register with IPA PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_offload_init(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to init offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_ep_register_interface(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to register EP interface"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Initialized device"); eth_dev->state = IPA_ETH_ST_INITED; return 0; } static int ipa_eth_deinit_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_DEINITED) return 0; if (eth_dev->state != IPA_ETH_ST_INITED) return -EFAULT; rc = ipa_eth_ep_unregister_interface(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to unregister IPA interface"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_offload_deinit(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to deinit offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_pm_unregister(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to unregister with IPA PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Deinitialized device"); eth_dev->state = IPA_ETH_ST_DEINITED; return 0; } static int ipa_eth_start_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_STARTED) return 0; if (eth_dev->state != IPA_ETH_ST_INITED) return -EFAULT; rc = ipa_eth_pm_activate(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to activate device PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_offload_start(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to start offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Started device"); eth_dev->state = IPA_ETH_ST_STARTED; return 0; } static int ipa_eth_stop_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_DEINITED) return 0; if (eth_dev->state != IPA_ETH_ST_STARTED) return -EFAULT; rc = ipa_eth_offload_stop(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to stop offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_pm_deactivate(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to deactivate device PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Stopped device"); eth_dev->state = IPA_ETH_ST_INITED; return 0; } static void __ipa_eth_refresh_device(struct ipa_eth_device *eth_dev) { ipa_eth_dev_log(eth_dev, "Refreshing offload state for device"); if (eth_dev->state == IPA_ETH_ST_ERROR) { ipa_eth_dev_err(eth_dev, "Device in ERROR state, skipping refresh"); return; } if (eth_dev->init) { if (eth_dev->state == IPA_ETH_ST_DEINITED) { (void) ipa_eth_init_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_INITED) { ipa_eth_dev_err(eth_dev, "Failed to init device"); return; } } } if (eth_dev->init && eth_dev->start && eth_dev->link_up) { (void) ipa_eth_start_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_STARTED) { ipa_eth_dev_err(eth_dev, "Failed to start device"); return; } if (ipa_eth_pm_vote_bw(eth_dev)) ipa_eth_dev_err(eth_dev, "Failed to vote for required BW"); } else { ipa_eth_dev_log(eth_dev, "Start is disallowed for the device"); if (eth_dev->state == IPA_ETH_ST_STARTED) { ipa_eth_stop_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_INITED) { ipa_eth_dev_err(eth_dev, "Failed to stop device"); return; } } } if (!eth_dev->init) { ipa_eth_dev_log(eth_dev, "Init is disallowed for the device"); ipa_eth_deinit_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_DEINITED) { ipa_eth_dev_err(eth_dev, "Failed to deinit device"); return; } } } static void ipa_eth_refresh_device(struct ipa_eth_device *eth_dev) { mutex_lock(&ipa_eth_devices_lock); if (ipa_eth_ready()) __ipa_eth_refresh_device(eth_dev); mutex_unlock(&ipa_eth_devices_lock); } static void ipa_eth_refresh_devices(void) { struct ipa_eth_device *eth_dev; mutex_lock(&ipa_eth_devices_lock); if (ipa_eth_ready()) { list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) { __ipa_eth_refresh_device(eth_dev); } } mutex_unlock(&ipa_eth_devices_lock); } static int ipa_eth_netdev_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *net_dev = netdev_notifier_info_to_dev(ptr); struct ipa_eth_device *eth_dev = container_of(nb, struct ipa_eth_device, netdevice_nb); if (net_dev != eth_dev->net_dev) return NOTIFY_DONE; ipa_eth_dev_log(eth_dev, "Received netdev event %lu", event); if (event == NETDEV_CHANGE) { eth_dev->link_up = !test_bit(__LINK_STATE_NOCARRIER, &net_dev->state); ipa_eth_refresh_device(eth_dev); } return NOTIFY_DONE; } static int ipa_eth_uc_ready_cb(struct notifier_block *nb, unsigned long action, void *data) { ipa_eth_log("IPA uC is ready"); ipa_eth_ipa_uc_is_ready = true; ipa_eth_refresh_devices(); return NOTIFY_OK; } static struct notifier_block uc_ready_cb = { .notifier_call = ipa_eth_uc_ready_cb, }; static void ipa_eth_ipa_ready_cb(void *data) { ipa_eth_log("IPA is ready"); ipa_eth_ipa_is_ready = true; ipa_eth_refresh_devices(); } struct ipa_eth_device *ipa_eth_find_device(struct device *dev) { struct ipa_eth_device *eth_dev; list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) { if (eth_dev->dev == dev) return eth_dev; } return NULL; } static ssize_t ipa_eth_dev_write_init(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret = debugfs_write_file_bool(file, user_buf, count, ppos); struct ipa_eth_device *eth_dev = container_of(file->private_data, struct ipa_eth_device, init); ipa_eth_refresh_device(eth_dev); return ret; } static const struct file_operations fops_eth_dev_init = { .read = debugfs_read_file_bool, .write = ipa_eth_dev_write_init, .open = simple_open, .llseek = default_llseek, }; static ssize_t ipa_eth_dev_write_start(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret = debugfs_write_file_bool(file, user_buf, count, ppos); struct ipa_eth_device *eth_dev = container_of(file->private_data, struct ipa_eth_device, start); ipa_eth_refresh_device(eth_dev); return ret; } static const struct file_operations fops_eth_dev_start = { .read = debugfs_read_file_bool, .write = ipa_eth_dev_write_start, .open = simple_open, .llseek = default_llseek, }; static ssize_t eth_dev_stats_print_one(char *buf, const size_t size, const char *dir, const char *link, struct ipa_eth_offload_link_stats *stats) { return scnprintf(buf, size, "%10s%10s%10s%10llu%10llu%10llu%10llu\n", dir, link, (stats->valid ? "yes" : "no"), stats->events, stats->frames, stats->packets, stats->octets); } static ssize_t eth_dev_stats_print(char *buf, const size_t size, struct ipa_eth_device *eth_dev) { ssize_t n = 0; struct ipa_eth_offload_stats stats; if (!eth_dev->od->ops->get_stats) return scnprintf(buf, size - n, "Not supported\n"); memset(&stats, 0, sizeof(stats)); if (eth_dev->od->ops->get_stats(eth_dev, &stats)) return scnprintf(buf, size - n, "Operation failed\n"); n += scnprintf(&buf[n], size - n, "%10s%10s%10s%10s%10s%10s%10s\n", "Dir", "Link", "Valid", "Events", "Frames", "Packets", "Octets"); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "ndev", &stats.rx.ndev); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "host", &stats.rx.host); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "uc", &stats.rx.uc); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "gsi", &stats.rx.gsi); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "ipa", &stats.rx.ipa); n += scnprintf(&buf[n], size - n, "\n"); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "ndev", &stats.tx.ndev); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "host", &stats.tx.host); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "uc", &stats.tx.uc); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "gsi", &stats.tx.gsi); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "ipa", &stats.tx.ipa); return n; } static ssize_t eth_dev_stats_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { ssize_t n; char *buf = NULL; struct ipa_eth_device *eth_dev = file->private_data; buf = kzalloc(2048, GFP_KERNEL); if (buf == NULL) return 0; n = eth_dev_stats_print(buf, sizeof(buf), eth_dev); n = simple_read_from_buffer(user_buf, count, ppos, buf, n); kfree(buf); return n; } static const struct file_operations fops_eth_dev_stats = { .read = eth_dev_stats_read, .open = simple_open, .llseek = default_llseek, }; static int ipa_eth_device_debugfs_create(struct ipa_eth_device *eth_dev) { eth_dev->debugfs = debugfs_create_dir(eth_dev->net_dev->name, ipa_eth_devices_debugfs); if (IS_ERR_OR_NULL(eth_dev->debugfs)) { ipa_eth_dev_err(eth_dev, "Failed to create debugfs root"); return -EFAULT; } debugfs_create_file("init", 0644, eth_dev->debugfs, ð_dev->init, &fops_eth_dev_init); debugfs_create_file("start", 0644, eth_dev->debugfs, ð_dev->start, &fops_eth_dev_start); debugfs_create_file("stats", 0644, eth_dev->debugfs, eth_dev, &fops_eth_dev_stats); return 0; } static void ipa_eth_device_debugfs_remove(struct ipa_eth_device *eth_dev) { debugfs_remove_recursive(eth_dev->debugfs); eth_dev->debugfs = NULL; } static int __ipa_eth_pair_device(struct ipa_eth_device *eth_dev) { int rc; if (ipa_eth_offload_device_paired(eth_dev)) { ipa_eth_dev_dbg(eth_dev, "Device already paired. Skipping."); return 0; } rc = ipa_eth_offload_pair_device(eth_dev); if (rc) { ipa_eth_dev_log(eth_dev, "Failed to pair device. Deferring."); return rc; } eth_dev->netdevice_nb.notifier_call = ipa_eth_netdev_event; rc = register_netdevice_notifier(ð_dev->netdevice_nb); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to register netdev notifier"); ipa_eth_offload_unpair_device(eth_dev); return rc; } (void) ipa_eth_device_debugfs_create(eth_dev); ipa_eth_dev_log(eth_dev, "Paired device with offload driver %s", eth_dev->od->name); return 0; } static void __ipa_eth_unpair_device(struct ipa_eth_device *eth_dev) { if (!ipa_eth_offload_device_paired(eth_dev)) { ipa_eth_dev_dbg(eth_dev, "Device already unpaired. Skipping."); return; } ipa_eth_dev_log(eth_dev, "Unpairing device from offload driver %s", eth_dev->od->name); ipa_eth_device_debugfs_remove(eth_dev); eth_dev->init = eth_dev->start = false; __ipa_eth_refresh_device(eth_dev); unregister_netdevice_notifier(ð_dev->netdevice_nb); ipa_eth_offload_unpair_device(eth_dev); } static void ipa_eth_pair_devices(void) { struct ipa_eth_device *eth_dev; mutex_lock(&ipa_eth_devices_lock); list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) (void) __ipa_eth_pair_device(eth_dev); mutex_unlock(&ipa_eth_devices_lock); } static void ipa_eth_unpair_devices(struct ipa_eth_offload_driver *od) { struct ipa_eth_device *eth_dev; mutex_lock(&ipa_eth_devices_lock); list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) { if (eth_dev->od == od) __ipa_eth_unpair_device(eth_dev); } mutex_unlock(&ipa_eth_devices_lock); } int ipa_eth_register_device(struct ipa_eth_device *eth_dev) { eth_dev->state = IPA_ETH_ST_DEINITED; eth_dev->pm_handle = IPA_PM_MAX_CLIENTS; eth_dev->init = eth_dev->start = !ipa_eth_noauto; mutex_lock(&ipa_eth_devices_lock); list_add(ð_dev->device_list, &ipa_eth_devices); (void) __ipa_eth_pair_device(eth_dev); mutex_unlock(&ipa_eth_devices_lock); return 0; } void ipa_eth_unregister_device(struct ipa_eth_device *eth_dev) { mutex_lock(&ipa_eth_devices_lock); __ipa_eth_unpair_device(eth_dev); list_del(ð_dev->device_list); mutex_unlock(&ipa_eth_devices_lock); } static phys_addr_t ipa_eth_vmalloc_to_pa(void *vaddr) { struct page *pg = vmalloc_to_page(vaddr); if (pg) return page_to_phys(pg); else return 0; } static phys_addr_t ipa_eth_va_to_pa(void *vaddr) { return is_vmalloc_addr(vaddr) ? ipa_eth_vmalloc_to_pa(vaddr) : virt_to_phys(vaddr); } /** * ipa_eth_iommu_map() - Create IOMMU mapping from a given DMA address to a * physical/virtual address * @domain: IOMMU domain in which the mapping need to be created * @daddr: DMA address (IO Virtual Address) that need to be mapped * @addr: Physical or CPU Virtual Address of memory * @is_va: True if @addr is CPU Virtual Address * @size: Total size of the mapping * @prot: Flags for iommu_map() call * @split: If True, separate page sized mapping is created * * Return: 0 on success, negative errno otherwise */ int ipa_eth_iommu_map(struct iommu_domain *domain, dma_addr_t daddr, void *addr, bool is_va, size_t size, int prot, bool split) { int rc; dma_addr_t daddr_r = rounddown(daddr, PAGE_SIZE); void *addr_r = (void *)rounddown((unsigned long)addr, PAGE_SIZE); size_t size_r = roundup(size + (daddr - daddr_r), PAGE_SIZE); const size_t MAP_SIZE = split ? PAGE_SIZE : size_r; if ((daddr - daddr_r) != (addr - addr_r)) { ipa_eth_err("Alignment mismatch between paddr and addr"); return -EINVAL; } if (daddr != daddr_r) ipa_eth_dbg("DMA address %p realigned to %p", daddr, daddr_r); if (addr != addr_r) ipa_eth_dbg("PA/VA address %p realigned to %p", addr, addr_r); if (size != size_r) ipa_eth_dbg("DMA map size %zx realigned to %zx", size, size_r); for (size = 0; size < size_r; size += MAP_SIZE, daddr_r += MAP_SIZE, addr_r += MAP_SIZE) { phys_addr_t paddr = is_va ? ipa_eth_va_to_pa(addr_r) : (phys_addr_t) (addr_r); phys_addr_t paddr_r = rounddown(paddr, PAGE_SIZE); if (!paddr_r) { rc = -EFAULT; ipa_eth_err("Failed to find paddr for vaddr %p", addr); goto failed_map; } if (paddr != paddr_r) { rc = -EFAULT; ipa_eth_err("paddr %p is not page aligned", paddr); goto failed_map; } rc = ipa3_iommu_map(domain, daddr_r, paddr_r, MAP_SIZE, prot); if (rc) { ipa_eth_err("Failed to map daddr %p to %s domain", daddr, domain->name); goto failed_map; } ipa_eth_log( "Mapped %zu bytes of daddr %p to paddr %p in domain %s", MAP_SIZE, daddr_r, paddr_r, domain->name); } return 0; failed_map: ipa_eth_iommu_unmap(domain, daddr_r - size, size, split); return rc; } /** * ipa_eth_iommu_unmap() - Remove an IOMMU mapping previously made using * ipa_eth_iommu_map() * @domain: IOMMU domain from which the mapping need to be removed * @daddr: DMA address (IO Virtual Address) that was mapped * @size: Total size of the mapping * @split: If True, separate page sized mappings were created * * Return: 0 on success, negative errno if at least one of the mappings could * not be removed. */ int ipa_eth_iommu_unmap(struct iommu_domain *domain, dma_addr_t daddr, size_t size, bool split) { int rc = 0; dma_addr_t daddr_r = rounddown(daddr, PAGE_SIZE); size_t size_r = roundup(size + (daddr - daddr_r), PAGE_SIZE); const size_t MAP_SIZE = split ? PAGE_SIZE : size_r; if (!size_r) { ipa_eth_dbg("Ignoring unmap request of size 0"); return 0; } for (size = 0; size < size_r; size += MAP_SIZE) { if (iommu_unmap(domain, daddr_r + size, MAP_SIZE) != MAP_SIZE) { rc = -EFAULT; ipa_eth_err("Failed to unmap daddr %p", daddr_r + size); } ipa_eth_log( "Unmapped %zu bytes of daddr %p in domain %s", MAP_SIZE, daddr_r + size, domain->name); } return rc; } /** * ipa_eth_register_net_driver() - Register a network driver with the offload * subsystem * @nd: Network driver to register * * Return: 0 on success, negative errno otherwise */ int ipa_eth_register_net_driver(struct ipa_eth_net_driver *nd) { return ipa_eth_bus_register_driver(nd); } EXPORT_SYMBOL(ipa_eth_register_net_driver); /** * ipa_eth_unregister_net_driver() - Unregister a network driver * @nd: Network driver to unregister */ void ipa_eth_unregister_net_driver(struct ipa_eth_net_driver *nd) { return ipa_eth_bus_unregister_driver(nd); } EXPORT_SYMBOL(ipa_eth_unregister_net_driver); /** * ipa_eth_register_offload_driver - Register an offload driver with the offload * subsystem * @nd: Offload driver to register * * Return: 0 on success, negative errno otherwise */ int ipa_eth_register_offload_driver(struct ipa_eth_offload_driver *od) { int rc; rc = ipa_eth_offload_register_driver(od); if (rc) { ipa_eth_err("Failed to register offload driver %s", od->name); return rc; } ipa_eth_pair_devices(); return 0; } EXPORT_SYMBOL(ipa_eth_register_offload_driver); /** * ipa_eth_unregister_offload_driver() - Unregister an offload driver * @nd: Offload driver to unregister */ void ipa_eth_unregister_offload_driver(struct ipa_eth_offload_driver *od) { ipa_eth_unpair_devices(od); ipa_eth_offload_unregister_driver(od); } EXPORT_SYMBOL(ipa_eth_unregister_offload_driver); static void ipa_eth_debugfs_cleanup(void) { debugfs_remove_recursive(ipa_eth_debugfs); } static int ipa_eth_debugfs_init(void) { int rc = 0; struct dentry *ipa_debugfs = ipa_debugfs_get_root(); if (IS_ERR_OR_NULL(ipa_debugfs)) return 0; ipa_eth_debugfs = debugfs_create_dir("ethernet", ipa_debugfs); if (IS_ERR_OR_NULL(ipa_eth_debugfs)) { ipa_eth_log("Unable to create debugfs root"); rc = ipa_eth_debugfs ? PTR_ERR(ipa_eth_debugfs) : -EFAULT; goto err_exit; } ipa_eth_drivers_debugfs = debugfs_create_dir("drivers", ipa_eth_debugfs); if (IS_ERR_OR_NULL(ipa_eth_drivers_debugfs)) { ipa_eth_log("Unable to create debugfs root for drivers"); rc = ipa_eth_drivers_debugfs ? PTR_ERR(ipa_eth_drivers_debugfs) : -EFAULT; goto err_exit; } ipa_eth_devices_debugfs = debugfs_create_dir("devices", ipa_eth_debugfs); if (IS_ERR_OR_NULL(ipa_eth_devices_debugfs)) { ipa_eth_log("Unable to create debugfs root for devices"); rc = ipa_eth_devices_debugfs ? PTR_ERR(ipa_eth_devices_debugfs) : -EFAULT; goto err_exit; } (void) debugfs_create_bool("ready", 0444, ipa_eth_debugfs, &ipa_eth_is_ready); (void) debugfs_create_bool("ipa_ready", 0444, ipa_eth_debugfs, &ipa_eth_ipa_is_ready); (void) debugfs_create_bool("uc_ready", 0444, ipa_eth_debugfs, &ipa_eth_ipa_uc_is_ready); return 0; err_exit: ipa_eth_debugfs_cleanup(); return rc; } int ipa_eth_init(void) { int rc; ipa_eth_dbg("Initializing IPA Ethernet Offload Sub-System"); rc = ipa_eth_debugfs_init(); if (rc) { ipa_eth_err("Failed to initialize debugfs"); goto err_dbgfs; } rc = ipa_eth_bus_modinit(ipa_eth_drivers_debugfs); if (rc) { ipa_eth_err("Failed to initialize bus"); goto err_bus; } rc = ipa_eth_offload_modinit(ipa_eth_drivers_debugfs); if (rc) { ipa_eth_err("Failed to initialize offload"); goto err_offload; } rc = ipa3_uc_register_ready_cb(&uc_ready_cb); if (rc) { ipa_eth_err("Failed to register for uC ready cb"); goto err_uc; } rc = ipa_register_ipa_ready_cb(ipa_eth_ipa_ready_cb, NULL); if (rc == -EEXIST) { ipa_eth_ipa_is_ready = true; } else if (rc) { ipa_eth_err("Failed to register for IPA ready cb"); goto err_ipa; } ipa_eth_is_ready = true; return 0; err_ipa: ipa3_uc_unregister_ready_cb(&uc_ready_cb); err_uc: ipa_eth_offload_modexit(); err_offload: ipa_eth_bus_modexit(); err_bus: ipa_eth_debugfs_cleanup(); err_dbgfs: return rc; } void ipa_eth_exit(void) { ipa_eth_dbg("De-initializing IPA Ethernet Offload Sub-System"); ipa_eth_is_ready = false; // IPA ready CB can not be unregistered; just unregister uC ready CB ipa3_uc_unregister_ready_cb(&uc_ready_cb); ipa_eth_offload_modexit(); ipa_eth_bus_modexit(); ipa_eth_debugfs_cleanup(); } Loading
drivers/platform/msm/Kconfig +19 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,25 @@ config IPA_WDI_UNIFIED_API The IPA WDI unified API supports all WDI versions through a unified interface. config IPA_ETH bool "IPA Ethernet Offload Sub-system support" depends on IPA3 help Enables IPA Ethernet Offload Subsystem for offloading PCI based ethernet devices to IPA. The offload subsystem still require a compatible network driver to register with it and a corresponding offload driver to manage one of more offload data paths that uses the network device. config IPA_ETH_NOAUTO bool "Disable automatic offload initialization of interfaces" depends on IPA_ETH help Enabling this option prevents automatic initialization of offload on ethernet interfaces. Debugfs control interface will instead be used to enable offloading. This feature is meant only for debugging. If unsure, say N. config RMNET_IPA3 tristate "IPA3 RMNET WWAN Network Device" depends on IPA3 && QCOM_QMI_HELPERS Loading
drivers/platform/msm/ipa/ipa_v3/Makefile +2 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,8 @@ obj-$(CONFIG_RMNET_IPA3) += rmnet_ipa.o ipa_qmi_service_v01.o ipa_qmi_service.o obj-$(CONFIG_IPA3_MHI_PROXY) += ipa_mhi_proxy.o obj-$(CONFIG_IPA_ETH) += ethernet/ ipat-$(CONFIG_IPA3_REGDUMP) += dump/ipa_reg_dump.o ccflags-$(CONFIG_IPA3_REGDUMP_SM8150) += -Idrivers/platform/msm/ipa/ipa_v3/dump/sm8150
drivers/platform/msm/ipa/ipa_v3/ethernet/Makefile 0 → 100644 +12 −0 Original line number Diff line number Diff line obj-$(CONFIG_IPA_ETH) += ipa-eth.o ipa-eth-y := \ ipa_eth_bus.o \ ipa_eth.o \ ipa_eth_ep.o \ ipa_eth_gsi.o \ ipa_eth_offload.o \ ipa_eth_pci.o \ ipa_eth_pm.o \ ipa_eth_uc.o
drivers/platform/msm/ipa/ipa_v3/ethernet/ipa_eth.c 0 → 100644 +902 −0 Original line number Diff line number Diff line /* Copyright (c) 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/printk.h> #include <linux/debugfs.h> #include <linux/module.h> #include <linux/msm_ipa.h> #include "ipa_eth_i.h" static bool ipa_eth_is_ready; static bool ipa_eth_ipa_is_ready; static bool ipa_eth_ipa_uc_is_ready; static struct dentry *ipa_eth_debugfs; static struct dentry *ipa_eth_drivers_debugfs; static struct dentry *ipa_eth_devices_debugfs; static LIST_HEAD(ipa_eth_devices); static DEFINE_MUTEX(ipa_eth_devices_lock); static bool ipa_eth_noauto = IPA_ETH_NOAUTO_DEFAULT; module_param(ipa_eth_noauto, bool, 0644); MODULE_PARM_DESC(ipa_eth_noauto, "Disable automatic offload initialization of interfaces"); static inline bool ipa_eth_ready(void) { return ipa_eth_is_ready && ipa_eth_ipa_is_ready && ipa_eth_ipa_uc_is_ready; } static int ipa_eth_init_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_INITED) return 0; if (eth_dev->state != IPA_ETH_ST_DEINITED) return -EFAULT; rc = ipa_eth_ep_init_headers(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to init EP headers"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_pm_register(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to register with IPA PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_offload_init(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to init offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_ep_register_interface(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to register EP interface"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Initialized device"); eth_dev->state = IPA_ETH_ST_INITED; return 0; } static int ipa_eth_deinit_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_DEINITED) return 0; if (eth_dev->state != IPA_ETH_ST_INITED) return -EFAULT; rc = ipa_eth_ep_unregister_interface(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to unregister IPA interface"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_offload_deinit(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to deinit offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_pm_unregister(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to unregister with IPA PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Deinitialized device"); eth_dev->state = IPA_ETH_ST_DEINITED; return 0; } static int ipa_eth_start_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_STARTED) return 0; if (eth_dev->state != IPA_ETH_ST_INITED) return -EFAULT; rc = ipa_eth_pm_activate(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to activate device PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_offload_start(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to start offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Started device"); eth_dev->state = IPA_ETH_ST_STARTED; return 0; } static int ipa_eth_stop_device(struct ipa_eth_device *eth_dev) { int rc; if (eth_dev->state == IPA_ETH_ST_DEINITED) return 0; if (eth_dev->state != IPA_ETH_ST_STARTED) return -EFAULT; rc = ipa_eth_offload_stop(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to stop offload"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } rc = ipa_eth_pm_deactivate(eth_dev); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to deactivate device PM"); eth_dev->state = IPA_ETH_ST_ERROR; return rc; } ipa_eth_dev_log(eth_dev, "Stopped device"); eth_dev->state = IPA_ETH_ST_INITED; return 0; } static void __ipa_eth_refresh_device(struct ipa_eth_device *eth_dev) { ipa_eth_dev_log(eth_dev, "Refreshing offload state for device"); if (eth_dev->state == IPA_ETH_ST_ERROR) { ipa_eth_dev_err(eth_dev, "Device in ERROR state, skipping refresh"); return; } if (eth_dev->init) { if (eth_dev->state == IPA_ETH_ST_DEINITED) { (void) ipa_eth_init_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_INITED) { ipa_eth_dev_err(eth_dev, "Failed to init device"); return; } } } if (eth_dev->init && eth_dev->start && eth_dev->link_up) { (void) ipa_eth_start_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_STARTED) { ipa_eth_dev_err(eth_dev, "Failed to start device"); return; } if (ipa_eth_pm_vote_bw(eth_dev)) ipa_eth_dev_err(eth_dev, "Failed to vote for required BW"); } else { ipa_eth_dev_log(eth_dev, "Start is disallowed for the device"); if (eth_dev->state == IPA_ETH_ST_STARTED) { ipa_eth_stop_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_INITED) { ipa_eth_dev_err(eth_dev, "Failed to stop device"); return; } } } if (!eth_dev->init) { ipa_eth_dev_log(eth_dev, "Init is disallowed for the device"); ipa_eth_deinit_device(eth_dev); if (eth_dev->state != IPA_ETH_ST_DEINITED) { ipa_eth_dev_err(eth_dev, "Failed to deinit device"); return; } } } static void ipa_eth_refresh_device(struct ipa_eth_device *eth_dev) { mutex_lock(&ipa_eth_devices_lock); if (ipa_eth_ready()) __ipa_eth_refresh_device(eth_dev); mutex_unlock(&ipa_eth_devices_lock); } static void ipa_eth_refresh_devices(void) { struct ipa_eth_device *eth_dev; mutex_lock(&ipa_eth_devices_lock); if (ipa_eth_ready()) { list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) { __ipa_eth_refresh_device(eth_dev); } } mutex_unlock(&ipa_eth_devices_lock); } static int ipa_eth_netdev_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *net_dev = netdev_notifier_info_to_dev(ptr); struct ipa_eth_device *eth_dev = container_of(nb, struct ipa_eth_device, netdevice_nb); if (net_dev != eth_dev->net_dev) return NOTIFY_DONE; ipa_eth_dev_log(eth_dev, "Received netdev event %lu", event); if (event == NETDEV_CHANGE) { eth_dev->link_up = !test_bit(__LINK_STATE_NOCARRIER, &net_dev->state); ipa_eth_refresh_device(eth_dev); } return NOTIFY_DONE; } static int ipa_eth_uc_ready_cb(struct notifier_block *nb, unsigned long action, void *data) { ipa_eth_log("IPA uC is ready"); ipa_eth_ipa_uc_is_ready = true; ipa_eth_refresh_devices(); return NOTIFY_OK; } static struct notifier_block uc_ready_cb = { .notifier_call = ipa_eth_uc_ready_cb, }; static void ipa_eth_ipa_ready_cb(void *data) { ipa_eth_log("IPA is ready"); ipa_eth_ipa_is_ready = true; ipa_eth_refresh_devices(); } struct ipa_eth_device *ipa_eth_find_device(struct device *dev) { struct ipa_eth_device *eth_dev; list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) { if (eth_dev->dev == dev) return eth_dev; } return NULL; } static ssize_t ipa_eth_dev_write_init(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret = debugfs_write_file_bool(file, user_buf, count, ppos); struct ipa_eth_device *eth_dev = container_of(file->private_data, struct ipa_eth_device, init); ipa_eth_refresh_device(eth_dev); return ret; } static const struct file_operations fops_eth_dev_init = { .read = debugfs_read_file_bool, .write = ipa_eth_dev_write_init, .open = simple_open, .llseek = default_llseek, }; static ssize_t ipa_eth_dev_write_start(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret = debugfs_write_file_bool(file, user_buf, count, ppos); struct ipa_eth_device *eth_dev = container_of(file->private_data, struct ipa_eth_device, start); ipa_eth_refresh_device(eth_dev); return ret; } static const struct file_operations fops_eth_dev_start = { .read = debugfs_read_file_bool, .write = ipa_eth_dev_write_start, .open = simple_open, .llseek = default_llseek, }; static ssize_t eth_dev_stats_print_one(char *buf, const size_t size, const char *dir, const char *link, struct ipa_eth_offload_link_stats *stats) { return scnprintf(buf, size, "%10s%10s%10s%10llu%10llu%10llu%10llu\n", dir, link, (stats->valid ? "yes" : "no"), stats->events, stats->frames, stats->packets, stats->octets); } static ssize_t eth_dev_stats_print(char *buf, const size_t size, struct ipa_eth_device *eth_dev) { ssize_t n = 0; struct ipa_eth_offload_stats stats; if (!eth_dev->od->ops->get_stats) return scnprintf(buf, size - n, "Not supported\n"); memset(&stats, 0, sizeof(stats)); if (eth_dev->od->ops->get_stats(eth_dev, &stats)) return scnprintf(buf, size - n, "Operation failed\n"); n += scnprintf(&buf[n], size - n, "%10s%10s%10s%10s%10s%10s%10s\n", "Dir", "Link", "Valid", "Events", "Frames", "Packets", "Octets"); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "ndev", &stats.rx.ndev); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "host", &stats.rx.host); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "uc", &stats.rx.uc); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "gsi", &stats.rx.gsi); n += eth_dev_stats_print_one(&buf[n], size - n, "rx", "ipa", &stats.rx.ipa); n += scnprintf(&buf[n], size - n, "\n"); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "ndev", &stats.tx.ndev); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "host", &stats.tx.host); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "uc", &stats.tx.uc); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "gsi", &stats.tx.gsi); n += eth_dev_stats_print_one(&buf[n], size - n, "tx", "ipa", &stats.tx.ipa); return n; } static ssize_t eth_dev_stats_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { ssize_t n; char *buf = NULL; struct ipa_eth_device *eth_dev = file->private_data; buf = kzalloc(2048, GFP_KERNEL); if (buf == NULL) return 0; n = eth_dev_stats_print(buf, sizeof(buf), eth_dev); n = simple_read_from_buffer(user_buf, count, ppos, buf, n); kfree(buf); return n; } static const struct file_operations fops_eth_dev_stats = { .read = eth_dev_stats_read, .open = simple_open, .llseek = default_llseek, }; static int ipa_eth_device_debugfs_create(struct ipa_eth_device *eth_dev) { eth_dev->debugfs = debugfs_create_dir(eth_dev->net_dev->name, ipa_eth_devices_debugfs); if (IS_ERR_OR_NULL(eth_dev->debugfs)) { ipa_eth_dev_err(eth_dev, "Failed to create debugfs root"); return -EFAULT; } debugfs_create_file("init", 0644, eth_dev->debugfs, ð_dev->init, &fops_eth_dev_init); debugfs_create_file("start", 0644, eth_dev->debugfs, ð_dev->start, &fops_eth_dev_start); debugfs_create_file("stats", 0644, eth_dev->debugfs, eth_dev, &fops_eth_dev_stats); return 0; } static void ipa_eth_device_debugfs_remove(struct ipa_eth_device *eth_dev) { debugfs_remove_recursive(eth_dev->debugfs); eth_dev->debugfs = NULL; } static int __ipa_eth_pair_device(struct ipa_eth_device *eth_dev) { int rc; if (ipa_eth_offload_device_paired(eth_dev)) { ipa_eth_dev_dbg(eth_dev, "Device already paired. Skipping."); return 0; } rc = ipa_eth_offload_pair_device(eth_dev); if (rc) { ipa_eth_dev_log(eth_dev, "Failed to pair device. Deferring."); return rc; } eth_dev->netdevice_nb.notifier_call = ipa_eth_netdev_event; rc = register_netdevice_notifier(ð_dev->netdevice_nb); if (rc) { ipa_eth_dev_err(eth_dev, "Failed to register netdev notifier"); ipa_eth_offload_unpair_device(eth_dev); return rc; } (void) ipa_eth_device_debugfs_create(eth_dev); ipa_eth_dev_log(eth_dev, "Paired device with offload driver %s", eth_dev->od->name); return 0; } static void __ipa_eth_unpair_device(struct ipa_eth_device *eth_dev) { if (!ipa_eth_offload_device_paired(eth_dev)) { ipa_eth_dev_dbg(eth_dev, "Device already unpaired. Skipping."); return; } ipa_eth_dev_log(eth_dev, "Unpairing device from offload driver %s", eth_dev->od->name); ipa_eth_device_debugfs_remove(eth_dev); eth_dev->init = eth_dev->start = false; __ipa_eth_refresh_device(eth_dev); unregister_netdevice_notifier(ð_dev->netdevice_nb); ipa_eth_offload_unpair_device(eth_dev); } static void ipa_eth_pair_devices(void) { struct ipa_eth_device *eth_dev; mutex_lock(&ipa_eth_devices_lock); list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) (void) __ipa_eth_pair_device(eth_dev); mutex_unlock(&ipa_eth_devices_lock); } static void ipa_eth_unpair_devices(struct ipa_eth_offload_driver *od) { struct ipa_eth_device *eth_dev; mutex_lock(&ipa_eth_devices_lock); list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) { if (eth_dev->od == od) __ipa_eth_unpair_device(eth_dev); } mutex_unlock(&ipa_eth_devices_lock); } int ipa_eth_register_device(struct ipa_eth_device *eth_dev) { eth_dev->state = IPA_ETH_ST_DEINITED; eth_dev->pm_handle = IPA_PM_MAX_CLIENTS; eth_dev->init = eth_dev->start = !ipa_eth_noauto; mutex_lock(&ipa_eth_devices_lock); list_add(ð_dev->device_list, &ipa_eth_devices); (void) __ipa_eth_pair_device(eth_dev); mutex_unlock(&ipa_eth_devices_lock); return 0; } void ipa_eth_unregister_device(struct ipa_eth_device *eth_dev) { mutex_lock(&ipa_eth_devices_lock); __ipa_eth_unpair_device(eth_dev); list_del(ð_dev->device_list); mutex_unlock(&ipa_eth_devices_lock); } static phys_addr_t ipa_eth_vmalloc_to_pa(void *vaddr) { struct page *pg = vmalloc_to_page(vaddr); if (pg) return page_to_phys(pg); else return 0; } static phys_addr_t ipa_eth_va_to_pa(void *vaddr) { return is_vmalloc_addr(vaddr) ? ipa_eth_vmalloc_to_pa(vaddr) : virt_to_phys(vaddr); } /** * ipa_eth_iommu_map() - Create IOMMU mapping from a given DMA address to a * physical/virtual address * @domain: IOMMU domain in which the mapping need to be created * @daddr: DMA address (IO Virtual Address) that need to be mapped * @addr: Physical or CPU Virtual Address of memory * @is_va: True if @addr is CPU Virtual Address * @size: Total size of the mapping * @prot: Flags for iommu_map() call * @split: If True, separate page sized mapping is created * * Return: 0 on success, negative errno otherwise */ int ipa_eth_iommu_map(struct iommu_domain *domain, dma_addr_t daddr, void *addr, bool is_va, size_t size, int prot, bool split) { int rc; dma_addr_t daddr_r = rounddown(daddr, PAGE_SIZE); void *addr_r = (void *)rounddown((unsigned long)addr, PAGE_SIZE); size_t size_r = roundup(size + (daddr - daddr_r), PAGE_SIZE); const size_t MAP_SIZE = split ? PAGE_SIZE : size_r; if ((daddr - daddr_r) != (addr - addr_r)) { ipa_eth_err("Alignment mismatch between paddr and addr"); return -EINVAL; } if (daddr != daddr_r) ipa_eth_dbg("DMA address %p realigned to %p", daddr, daddr_r); if (addr != addr_r) ipa_eth_dbg("PA/VA address %p realigned to %p", addr, addr_r); if (size != size_r) ipa_eth_dbg("DMA map size %zx realigned to %zx", size, size_r); for (size = 0; size < size_r; size += MAP_SIZE, daddr_r += MAP_SIZE, addr_r += MAP_SIZE) { phys_addr_t paddr = is_va ? ipa_eth_va_to_pa(addr_r) : (phys_addr_t) (addr_r); phys_addr_t paddr_r = rounddown(paddr, PAGE_SIZE); if (!paddr_r) { rc = -EFAULT; ipa_eth_err("Failed to find paddr for vaddr %p", addr); goto failed_map; } if (paddr != paddr_r) { rc = -EFAULT; ipa_eth_err("paddr %p is not page aligned", paddr); goto failed_map; } rc = ipa3_iommu_map(domain, daddr_r, paddr_r, MAP_SIZE, prot); if (rc) { ipa_eth_err("Failed to map daddr %p to %s domain", daddr, domain->name); goto failed_map; } ipa_eth_log( "Mapped %zu bytes of daddr %p to paddr %p in domain %s", MAP_SIZE, daddr_r, paddr_r, domain->name); } return 0; failed_map: ipa_eth_iommu_unmap(domain, daddr_r - size, size, split); return rc; } /** * ipa_eth_iommu_unmap() - Remove an IOMMU mapping previously made using * ipa_eth_iommu_map() * @domain: IOMMU domain from which the mapping need to be removed * @daddr: DMA address (IO Virtual Address) that was mapped * @size: Total size of the mapping * @split: If True, separate page sized mappings were created * * Return: 0 on success, negative errno if at least one of the mappings could * not be removed. */ int ipa_eth_iommu_unmap(struct iommu_domain *domain, dma_addr_t daddr, size_t size, bool split) { int rc = 0; dma_addr_t daddr_r = rounddown(daddr, PAGE_SIZE); size_t size_r = roundup(size + (daddr - daddr_r), PAGE_SIZE); const size_t MAP_SIZE = split ? PAGE_SIZE : size_r; if (!size_r) { ipa_eth_dbg("Ignoring unmap request of size 0"); return 0; } for (size = 0; size < size_r; size += MAP_SIZE) { if (iommu_unmap(domain, daddr_r + size, MAP_SIZE) != MAP_SIZE) { rc = -EFAULT; ipa_eth_err("Failed to unmap daddr %p", daddr_r + size); } ipa_eth_log( "Unmapped %zu bytes of daddr %p in domain %s", MAP_SIZE, daddr_r + size, domain->name); } return rc; } /** * ipa_eth_register_net_driver() - Register a network driver with the offload * subsystem * @nd: Network driver to register * * Return: 0 on success, negative errno otherwise */ int ipa_eth_register_net_driver(struct ipa_eth_net_driver *nd) { return ipa_eth_bus_register_driver(nd); } EXPORT_SYMBOL(ipa_eth_register_net_driver); /** * ipa_eth_unregister_net_driver() - Unregister a network driver * @nd: Network driver to unregister */ void ipa_eth_unregister_net_driver(struct ipa_eth_net_driver *nd) { return ipa_eth_bus_unregister_driver(nd); } EXPORT_SYMBOL(ipa_eth_unregister_net_driver); /** * ipa_eth_register_offload_driver - Register an offload driver with the offload * subsystem * @nd: Offload driver to register * * Return: 0 on success, negative errno otherwise */ int ipa_eth_register_offload_driver(struct ipa_eth_offload_driver *od) { int rc; rc = ipa_eth_offload_register_driver(od); if (rc) { ipa_eth_err("Failed to register offload driver %s", od->name); return rc; } ipa_eth_pair_devices(); return 0; } EXPORT_SYMBOL(ipa_eth_register_offload_driver); /** * ipa_eth_unregister_offload_driver() - Unregister an offload driver * @nd: Offload driver to unregister */ void ipa_eth_unregister_offload_driver(struct ipa_eth_offload_driver *od) { ipa_eth_unpair_devices(od); ipa_eth_offload_unregister_driver(od); } EXPORT_SYMBOL(ipa_eth_unregister_offload_driver); static void ipa_eth_debugfs_cleanup(void) { debugfs_remove_recursive(ipa_eth_debugfs); } static int ipa_eth_debugfs_init(void) { int rc = 0; struct dentry *ipa_debugfs = ipa_debugfs_get_root(); if (IS_ERR_OR_NULL(ipa_debugfs)) return 0; ipa_eth_debugfs = debugfs_create_dir("ethernet", ipa_debugfs); if (IS_ERR_OR_NULL(ipa_eth_debugfs)) { ipa_eth_log("Unable to create debugfs root"); rc = ipa_eth_debugfs ? PTR_ERR(ipa_eth_debugfs) : -EFAULT; goto err_exit; } ipa_eth_drivers_debugfs = debugfs_create_dir("drivers", ipa_eth_debugfs); if (IS_ERR_OR_NULL(ipa_eth_drivers_debugfs)) { ipa_eth_log("Unable to create debugfs root for drivers"); rc = ipa_eth_drivers_debugfs ? PTR_ERR(ipa_eth_drivers_debugfs) : -EFAULT; goto err_exit; } ipa_eth_devices_debugfs = debugfs_create_dir("devices", ipa_eth_debugfs); if (IS_ERR_OR_NULL(ipa_eth_devices_debugfs)) { ipa_eth_log("Unable to create debugfs root for devices"); rc = ipa_eth_devices_debugfs ? PTR_ERR(ipa_eth_devices_debugfs) : -EFAULT; goto err_exit; } (void) debugfs_create_bool("ready", 0444, ipa_eth_debugfs, &ipa_eth_is_ready); (void) debugfs_create_bool("ipa_ready", 0444, ipa_eth_debugfs, &ipa_eth_ipa_is_ready); (void) debugfs_create_bool("uc_ready", 0444, ipa_eth_debugfs, &ipa_eth_ipa_uc_is_ready); return 0; err_exit: ipa_eth_debugfs_cleanup(); return rc; } int ipa_eth_init(void) { int rc; ipa_eth_dbg("Initializing IPA Ethernet Offload Sub-System"); rc = ipa_eth_debugfs_init(); if (rc) { ipa_eth_err("Failed to initialize debugfs"); goto err_dbgfs; } rc = ipa_eth_bus_modinit(ipa_eth_drivers_debugfs); if (rc) { ipa_eth_err("Failed to initialize bus"); goto err_bus; } rc = ipa_eth_offload_modinit(ipa_eth_drivers_debugfs); if (rc) { ipa_eth_err("Failed to initialize offload"); goto err_offload; } rc = ipa3_uc_register_ready_cb(&uc_ready_cb); if (rc) { ipa_eth_err("Failed to register for uC ready cb"); goto err_uc; } rc = ipa_register_ipa_ready_cb(ipa_eth_ipa_ready_cb, NULL); if (rc == -EEXIST) { ipa_eth_ipa_is_ready = true; } else if (rc) { ipa_eth_err("Failed to register for IPA ready cb"); goto err_ipa; } ipa_eth_is_ready = true; return 0; err_ipa: ipa3_uc_unregister_ready_cb(&uc_ready_cb); err_uc: ipa_eth_offload_modexit(); err_offload: ipa_eth_bus_modexit(); err_bus: ipa_eth_debugfs_cleanup(); err_dbgfs: return rc; } void ipa_eth_exit(void) { ipa_eth_dbg("De-initializing IPA Ethernet Offload Sub-System"); ipa_eth_is_ready = false; // IPA ready CB can not be unregistered; just unregister uC ready CB ipa3_uc_unregister_ready_cb(&uc_ready_cb); ipa_eth_offload_modexit(); ipa_eth_bus_modexit(); ipa_eth_debugfs_cleanup(); }