mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-09 21:59:12 -04:00
Merge "msm: ipa: IPA offload subsystem for ethernet devices"
This commit is contained in:
commit
c0a8ba67dd
19 changed files with 7093 additions and 0 deletions
|
|
@ -143,4 +143,32 @@ config R8125
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called r8125.
|
||||
|
||||
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 IPA_ETH_DEBUG
|
||||
bool "Enable addtional debug logging in the offload sub-system"
|
||||
depends on IPA_ETH
|
||||
help
|
||||
Enabling this option increases the number of messages logged by the offload
|
||||
sub-system for aiding debugging. Additional debug messages are logged into
|
||||
IPC log as well as kernel log using dynamic debug.
|
||||
If unsure, say N.
|
||||
|
||||
endmenu
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@ obj-$(CONFIG_SPS) += sps/
|
|||
obj-$(CONFIG_MSM_11AD) += msm_11ad/
|
||||
obj-$(CONFIG_EP_PCIE) += ep_pcie/
|
||||
obj-$(CONFIG_MSM_MHI_DEV) += mhi_dev/
|
||||
obj-$(CONFIG_IPA_ETH) += ethernet/
|
||||
|
|
|
|||
21
drivers/platform/msm/ethernet/Makefile
Normal file
21
drivers/platform/msm/ethernet/Makefile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#
|
||||
# Makefile for the MSM specific device drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_IPA_ETH) += ipa-eth.o
|
||||
|
||||
ipa-eth-y := \
|
||||
ipa_eth_bus.o \
|
||||
ipa_eth.o \
|
||||
ipa_eth_debugfs.o \
|
||||
ipa_eth_ep.o \
|
||||
ipa_eth_gsi.o \
|
||||
ipa_eth_net.o \
|
||||
ipa_eth_offload.o \
|
||||
ipa_eth_pci.o \
|
||||
ipa_eth_pm.o \
|
||||
ipa_eth_trace.o \
|
||||
ipa_eth_uc.o \
|
||||
ipa_eth_utils.o
|
||||
1135
drivers/platform/msm/ethernet/ipa_eth.c
Normal file
1135
drivers/platform/msm/ethernet/ipa_eth.c
Normal file
File diff suppressed because it is too large
Load diff
136
drivers/platform/msm/ethernet/ipa_eth_bus.c
Normal file
136
drivers/platform/msm/ethernet/ipa_eth_bus.c
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
static bool ipa_eth_bus_is_ready;
|
||||
|
||||
struct ipa_eth_bus_map {
|
||||
struct bus_type *bus;
|
||||
struct ipa_eth_bus *eth_bus;
|
||||
int (*modinit)(void);
|
||||
void (*modexit)(void);
|
||||
} static const bus_map[] = {
|
||||
{
|
||||
&pci_bus_type,
|
||||
&ipa_eth_pci_bus,
|
||||
&ipa_eth_pci_modinit,
|
||||
&ipa_eth_pci_modexit
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct ipa_eth_bus *lookup_eth_bus(struct bus_type *bus)
|
||||
{
|
||||
const struct ipa_eth_bus_map *map;
|
||||
|
||||
for (map = bus_map; map->bus != NULL; map++) {
|
||||
if (map->bus == bus)
|
||||
return map->eth_bus;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ipa_eth_bus_register_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
struct ipa_eth_bus *eth_bus;
|
||||
|
||||
eth_bus = lookup_eth_bus(nd->bus);
|
||||
if (!eth_bus) {
|
||||
ipa_eth_err("Unsupported bus %s", nd->bus->name);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return eth_bus->register_driver(nd);
|
||||
}
|
||||
|
||||
void ipa_eth_bus_unregister_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
struct ipa_eth_bus *eth_bus = lookup_eth_bus(nd->bus);
|
||||
|
||||
if (!eth_bus) {
|
||||
ipa_eth_bug("Failed to lookup eth_bus for %s", nd->bus->name);
|
||||
return;
|
||||
}
|
||||
|
||||
eth_bus->unregister_driver(nd);
|
||||
}
|
||||
|
||||
int ipa_eth_bus_enable_pc(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
struct ipa_eth_net_driver *nd = eth_dev->nd;
|
||||
struct ipa_eth_bus *eth_bus = lookup_eth_bus(nd->bus);
|
||||
|
||||
if (!eth_bus) {
|
||||
ipa_eth_dev_bug(eth_dev, "Failed to lookup eth_bus");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (eth_bus->enable_pc)
|
||||
return eth_bus->enable_pc(eth_dev);
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
int ipa_eth_bus_disable_pc(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
struct ipa_eth_net_driver *nd = eth_dev->nd;
|
||||
struct ipa_eth_bus *eth_bus = lookup_eth_bus(nd->bus);
|
||||
|
||||
if (!eth_bus) {
|
||||
ipa_eth_dev_bug(eth_dev, "Failed to lookup eth_bus");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (eth_bus->disable_pc)
|
||||
return eth_bus->disable_pc(eth_dev);
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
int ipa_eth_bus_modinit(void)
|
||||
{
|
||||
int rc;
|
||||
const struct ipa_eth_bus_map *map;
|
||||
|
||||
/* initialize all registered busses */
|
||||
for (rc = 0, map = bus_map; map->bus != NULL; map++)
|
||||
rc |= map->modinit();
|
||||
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to initialize one or more busses");
|
||||
goto err_init;
|
||||
}
|
||||
|
||||
ipa_eth_bus_is_ready = true;
|
||||
|
||||
ipa_eth_log("Offload sub-system bus module init is complete");
|
||||
|
||||
return 0;
|
||||
|
||||
err_init:
|
||||
for (map = bus_map; map->bus != NULL; map++)
|
||||
map->modexit();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void ipa_eth_bus_modexit(void)
|
||||
{
|
||||
const struct ipa_eth_bus_map *map;
|
||||
|
||||
ipa_eth_log("De-initing offload sub-system bus module");
|
||||
|
||||
if (!ipa_eth_bus_is_ready)
|
||||
return;
|
||||
|
||||
ipa_eth_bus_is_ready = false;
|
||||
|
||||
for (map = bus_map; map->bus != NULL; map++)
|
||||
map->modexit();
|
||||
}
|
||||
307
drivers/platform/msm/ethernet/ipa_eth_debugfs.c
Normal file
307
drivers/platform/msm/ethernet/ipa_eth_debugfs.c
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
#include "ipa_eth_debugfs.h"
|
||||
|
||||
static struct dentry *ipa_eth_debugfs;
|
||||
|
||||
static struct dentry *ipa_eth_devices_debugfs;
|
||||
static struct dentry *ipa_eth_drivers_debugfs;
|
||||
|
||||
static struct dentry *ipa_eth_bus_debugfs;
|
||||
static struct dentry *ipa_eth_pci_debugfs;
|
||||
static struct dentry *ipa_eth_offload_debugfs;
|
||||
|
||||
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_device_refresh_sync(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);
|
||||
|
||||
/* Set/reset timer to automatically start offload after the timeout
|
||||
* specified in eth_dev->start_on_timeout (milliseconds) expires.
|
||||
*/
|
||||
if (!eth_dev->start && eth_dev->start_on_timeout)
|
||||
mod_timer(ð_dev->start_timer,
|
||||
jiffies + msecs_to_jiffies(eth_dev->start_on_timeout));
|
||||
|
||||
ipa_eth_device_refresh_sync(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 = 0, size = 2048;
|
||||
char *buf = NULL;
|
||||
struct ipa_eth_device *eth_dev = file->private_data;
|
||||
|
||||
buf = kzalloc(size, GFP_KERNEL);
|
||||
if (buf == NULL)
|
||||
return 0;
|
||||
|
||||
n = eth_dev_stats_print(buf, size, 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,
|
||||
};
|
||||
|
||||
int ipa_eth_debugfs_add_device(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_bool("start_on_wakeup", 0644,
|
||||
eth_dev->debugfs, ð_dev->start_on_wakeup);
|
||||
|
||||
debugfs_create_bool("start_on_resume", 0644,
|
||||
eth_dev->debugfs, ð_dev->start_on_resume);
|
||||
|
||||
debugfs_create_u32("start_on_timeout", 0644, eth_dev->debugfs,
|
||||
ð_dev->start_on_timeout);
|
||||
|
||||
debugfs_create_file("stats", 0644, eth_dev->debugfs, eth_dev,
|
||||
&fops_eth_dev_stats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ipa_eth_debugfs_remove_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
debugfs_remove_recursive(eth_dev->debugfs);
|
||||
eth_dev->debugfs = NULL;
|
||||
}
|
||||
|
||||
int ipa_eth_debugfs_add_offload_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
if (!od->debugfs && ipa_eth_offload_debugfs) {
|
||||
od->debugfs =
|
||||
debugfs_create_dir(od->name, ipa_eth_offload_debugfs);
|
||||
if (!IS_ERR_OR_NULL(od->debugfs))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
void ipa_eth_debugfs_remove_offload_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
debugfs_remove_recursive(od->debugfs);
|
||||
od->debugfs = NULL;
|
||||
}
|
||||
|
||||
static ssize_t eth_dev_ready_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
char *buf;
|
||||
ssize_t n = 0, size = 128;
|
||||
|
||||
buf = kzalloc(size, GFP_KERNEL);
|
||||
if (buf == NULL)
|
||||
return 0;
|
||||
|
||||
n += scnprintf(&buf[n], size - n, "Offload Sub-system: %s\n",
|
||||
test_bit(IPA_ETH_ST_READY, &ipa_eth_state) ?
|
||||
"Ready" : "Not Ready");
|
||||
|
||||
n += scnprintf(&buf[n], size - n, "IPA API: %s\n",
|
||||
test_bit(IPA_ETH_ST_API_READY, &ipa_eth_state) ?
|
||||
"Ready" : "Not Ready");
|
||||
|
||||
n += scnprintf(&buf[n], size - n, "ALL: %s\n",
|
||||
ipa_eth_all_ready() ? "Ready" : "Not Ready");
|
||||
|
||||
n = simple_read_from_buffer(user_buf, count, ppos, buf, n);
|
||||
|
||||
kfree(buf);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static const struct file_operations fops_eth_dev_ready = {
|
||||
.read = eth_dev_ready_read,
|
||||
.open = simple_open,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
int ipa_eth_debugfs_init(void)
|
||||
{
|
||||
struct dentry *ipa_debugfs = ipa_debugfs_get_root();
|
||||
|
||||
if (IS_ERR_OR_NULL(ipa_debugfs))
|
||||
return -EFAULT;
|
||||
|
||||
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");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
(void) debugfs_create_file("ready", 0644, ipa_eth_debugfs, NULL,
|
||||
&fops_eth_dev_ready);
|
||||
|
||||
(void) debugfs_create_bool("no_auto", 0644,
|
||||
ipa_eth_debugfs, &ipa_eth_noauto);
|
||||
|
||||
(void) debugfs_create_bool("ipc_logdbg", 0644,
|
||||
ipa_eth_debugfs, &ipa_eth_ipc_logdbg);
|
||||
|
||||
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");
|
||||
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");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
ipa_eth_bus_debugfs =
|
||||
debugfs_create_dir("bus", ipa_eth_drivers_debugfs);
|
||||
if (IS_ERR_OR_NULL(ipa_eth_bus_debugfs)) {
|
||||
ipa_eth_log("Unable to create debugfs root for bus");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
ipa_eth_pci_debugfs = debugfs_create_dir("pci", ipa_eth_bus_debugfs);
|
||||
if (IS_ERR_OR_NULL(ipa_eth_pci_debugfs)) {
|
||||
ipa_eth_log("Unable to create debugfs root for pci");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
ipa_eth_offload_debugfs =
|
||||
debugfs_create_dir("offload", ipa_eth_drivers_debugfs);
|
||||
if (IS_ERR_OR_NULL(ipa_eth_offload_debugfs)) {
|
||||
ipa_eth_log("Unable to create debugfs root for offload");
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
ipa_eth_log("Debugfs root is initialized");
|
||||
|
||||
return 0;
|
||||
|
||||
err_exit:
|
||||
ipa_eth_debugfs_cleanup();
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
void ipa_eth_debugfs_cleanup(void)
|
||||
{
|
||||
debugfs_remove_recursive(ipa_eth_debugfs);
|
||||
}
|
||||
20
drivers/platform/msm/ethernet/ipa_eth_debugfs.h
Normal file
20
drivers/platform/msm/ethernet/ipa_eth_debugfs.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef _IPA_ETH_DEBUGFS_H_
|
||||
#define _IPA_ETH_DEBUGFS_H_
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
int ipa_eth_debugfs_add_device(struct ipa_eth_device *eth_dev);
|
||||
void ipa_eth_debugfs_remove_device(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_debugfs_add_offload_driver(struct ipa_eth_offload_driver *od);
|
||||
void ipa_eth_debugfs_remove_offload_driver(struct ipa_eth_offload_driver *od);
|
||||
|
||||
int ipa_eth_debugfs_init(void);
|
||||
void ipa_eth_debugfs_cleanup(void);
|
||||
|
||||
#endif /* _IPA_ETH_DEBUGFS_H_ */
|
||||
680
drivers/platform/msm/ethernet/ipa_eth_ep.c
Normal file
680
drivers/platform/msm/ethernet/ipa_eth_ep.c
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/if_vlan.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
#include "ipa_eth_trace.h"
|
||||
|
||||
#ifndef IPA_ETH_EP_LOOPBACK
|
||||
static int __handle_ipa_receive(struct ipa_eth_channel *ch,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
if (!ch->process_skb) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
return ch->process_skb(ch, skb);
|
||||
}
|
||||
#else
|
||||
static int __handle_ipa_receive(struct ipa_eth_channel *ch,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
if (ipa_tx_dp(IPA_CLIENT_AQC_ETHERNET_CONS, skb, NULL)) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
ch->exception_loopback++;
|
||||
|
||||
return NET_RX_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void handle_ipa_receive(struct ipa_eth_channel *ch,
|
||||
unsigned long data)
|
||||
{
|
||||
bool tracing = trace_lan_rx_skb_enabled();
|
||||
struct sk_buff *skb = (struct sk_buff *) data;
|
||||
|
||||
ch->exception_total++;
|
||||
|
||||
/* Keep skb from being freed until tracing is completed */
|
||||
if (tracing)
|
||||
skb_get(skb);
|
||||
|
||||
if (__handle_ipa_receive(ch, skb) == NET_RX_DROP)
|
||||
ch->exception_drops++;
|
||||
|
||||
if (tracing) {
|
||||
trace_lan_rx_skb(ch, skb);
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
}
|
||||
|
||||
static void ipa_ep_client_notifier(void *priv, enum ipa_dp_evt_type evt,
|
||||
unsigned long data)
|
||||
{
|
||||
struct ipa_eth_channel *ch = (struct ipa_eth_channel *) priv;
|
||||
|
||||
if (evt == IPA_RECEIVE)
|
||||
handle_ipa_receive(ch, data);
|
||||
}
|
||||
|
||||
enum ipa_eth_phdr_type {
|
||||
IPA_ETH_PHDR_V4,
|
||||
IPA_ETH_PHDR_V6,
|
||||
IPA_ETH_PHDR_MAX = IPA_ETH_PHDR_V6,
|
||||
};
|
||||
|
||||
#define IPA_ETH_PHDR_NUM (IPA_ETH_PHDR_MAX + 1)
|
||||
|
||||
struct ipa_eth_phdr_add_ioc {
|
||||
struct ipa_ioc_add_hdr ioc;
|
||||
struct ipa_hdr_add hdrs[IPA_ETH_PHDR_NUM];
|
||||
};
|
||||
|
||||
static const struct ipa_eth_phdr_add_ioc ADD_HDR_TEMPLATE = {
|
||||
.ioc = {
|
||||
.commit = 1,
|
||||
.num_hdrs = IPA_ETH_PHDR_NUM,
|
||||
},
|
||||
.hdrs = {
|
||||
[IPA_ETH_PHDR_V4] = {
|
||||
.is_partial = 1,
|
||||
.is_eth2_ofst_valid = 1,
|
||||
.eth2_ofst = 0,
|
||||
},
|
||||
[IPA_ETH_PHDR_V6] = {
|
||||
.is_partial = 1,
|
||||
.is_eth2_ofst_valid = 1,
|
||||
.eth2_ofst = 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static inline __be16 phdr_type_to_proto(enum ipa_eth_phdr_type type)
|
||||
{
|
||||
return (type == IPA_ETH_PHDR_V4) ?
|
||||
htons(ETH_P_IP) : htons(ETH_P_IPV6);
|
||||
}
|
||||
|
||||
static u8 ipa_eth_init_ethhdr(struct ethhdr *eth_hdr,
|
||||
enum ipa_eth_phdr_type type,
|
||||
struct net_device *net_dev)
|
||||
{
|
||||
memset(eth_hdr, 0, sizeof(*eth_hdr));
|
||||
|
||||
eth_hdr->h_proto = phdr_type_to_proto(type);
|
||||
memcpy(ð_hdr->h_source, net_dev->dev_addr, ETH_ALEN);
|
||||
|
||||
return ETH_HLEN;
|
||||
}
|
||||
|
||||
static u8 ipa_eth_init_vlan_ethhdr(struct vlan_ethhdr *eth_hdr,
|
||||
enum ipa_eth_phdr_type type,
|
||||
struct net_device *net_dev)
|
||||
{
|
||||
memset(eth_hdr, 0, sizeof(*eth_hdr));
|
||||
|
||||
eth_hdr->h_vlan_proto = htons(ETH_P_8021Q);
|
||||
eth_hdr->h_vlan_encapsulated_proto = phdr_type_to_proto(type);
|
||||
memcpy(ð_hdr->h_source, net_dev->dev_addr, ETH_ALEN);
|
||||
|
||||
return VLAN_ETH_HLEN;
|
||||
}
|
||||
|
||||
static void ipa_eth_init_hdr_add(struct ipa_hdr_add *hdr_add,
|
||||
bool vlan_mode,
|
||||
enum ipa_eth_phdr_type type,
|
||||
struct net_device *net_dev)
|
||||
{
|
||||
const char *fmt_str = (type == IPA_ETH_PHDR_V4) ? "%s_ipv4" : "%s_ipv6";
|
||||
|
||||
snprintf(hdr_add->name, sizeof(hdr_add->name), fmt_str, net_dev->name);
|
||||
|
||||
hdr_add->hdr_len = vlan_mode ?
|
||||
ipa_eth_init_vlan_ethhdr((void *)hdr_add->hdr, type, net_dev) :
|
||||
ipa_eth_init_ethhdr((void *)hdr_add->hdr, type, net_dev);
|
||||
|
||||
hdr_add->type = vlan_mode ? IPA_HDR_L2_802_1Q : IPA_HDR_L2_ETHERNET_II;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_init_headers() - Install partial headers
|
||||
* @eth_dev: Offload device
|
||||
*
|
||||
* Installs partial headers in IPA for IPv4 and IPv6 traffic.
|
||||
*
|
||||
* Return: 0 on success, negative errno code otherwise
|
||||
*/
|
||||
int ipa_eth_ep_init_headers(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
bool vlan_mode;
|
||||
struct net_device *net_dev = eth_dev->net_dev;
|
||||
struct ipa_eth_phdr_add_ioc phdr_add = ADD_HDR_TEMPLATE;
|
||||
struct ipa_hdr_add *hdr_v4 = &phdr_add.hdrs[IPA_ETH_PHDR_V4];
|
||||
struct ipa_hdr_add *hdr_v6 = &phdr_add.hdrs[IPA_ETH_PHDR_V6];
|
||||
|
||||
rc = ipa3_is_vlan_mode(IPA_VLAN_IF_ETH, &vlan_mode);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Could not determine IPA VLAN mode");
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_init_hdr_add(hdr_v4, vlan_mode, IPA_ETH_PHDR_V4, net_dev);
|
||||
ipa_eth_init_hdr_add(hdr_v6, vlan_mode, IPA_ETH_PHDR_V6, net_dev);
|
||||
|
||||
rc = ipa_add_hdr(&phdr_add.ioc);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to install partial headers");
|
||||
} else {
|
||||
eth_dev->phdr_v4_handle = hdr_v4->hdr_hdl;
|
||||
eth_dev->phdr_v6_handle = hdr_v6->hdr_hdl;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct ipa_eth_phdr_del_ioc {
|
||||
struct ipa_ioc_del_hdr ioc;
|
||||
struct ipa_hdr_del hdrs[IPA_ETH_PHDR_NUM];
|
||||
};
|
||||
|
||||
static const struct ipa_eth_phdr_del_ioc DEL_HDR_TEMPLATE = {
|
||||
.ioc = {
|
||||
.commit = 1,
|
||||
.num_hdls = IPA_ETH_PHDR_NUM,
|
||||
},
|
||||
};
|
||||
|
||||
int ipa_eth_ep_deinit_headers(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct ipa_eth_phdr_del_ioc phdr_del = DEL_HDR_TEMPLATE;
|
||||
|
||||
phdr_del.hdrs[IPA_ETH_PHDR_V4].hdl = eth_dev->phdr_v4_handle;
|
||||
phdr_del.hdrs[IPA_ETH_PHDR_V6].hdl = eth_dev->phdr_v6_handle;
|
||||
|
||||
rc = ipa_del_hdr(&phdr_del.ioc);
|
||||
if (rc)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to remove partial headers");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static inline size_t __list_size(struct list_head *head)
|
||||
{
|
||||
size_t count = 0;
|
||||
struct list_head *l;
|
||||
|
||||
list_for_each(l, head)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void ipa_eth_ep_init_rx_props(
|
||||
struct ipa_ioc_rx_intf_prop *props,
|
||||
struct ipa_eth_channel *ch,
|
||||
bool is_ipv4, bool vlan_mode)
|
||||
{
|
||||
props->ip = is_ipv4 ? IPA_IP_v4 : IPA_IP_v6;
|
||||
props->src_pipe = ch->ipa_client;
|
||||
props->hdr_l2_type = vlan_mode ?
|
||||
IPA_HDR_L2_802_1Q : IPA_HDR_L2_ETHERNET_II;
|
||||
}
|
||||
|
||||
static void ipa_eth_ep_init_tx_props(
|
||||
struct ipa_ioc_tx_intf_prop *props,
|
||||
struct ipa_eth_channel *ch,
|
||||
bool is_ipv4, bool vlan_mode)
|
||||
{
|
||||
const char *fmt_str = is_ipv4 ? "%s_ipv4" : "%s_ipv6";
|
||||
|
||||
props->ip = is_ipv4 ? IPA_IP_v4 : IPA_IP_v6;
|
||||
props->dst_pipe = ch->ipa_client;
|
||||
props->hdr_l2_type = vlan_mode ?
|
||||
IPA_HDR_L2_802_1Q : IPA_HDR_L2_ETHERNET_II;
|
||||
|
||||
snprintf(props->hdr_name, sizeof(props->hdr_name), fmt_str,
|
||||
ch->eth_dev->net_dev->name);
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_init_tx_intf(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
bool vlan_mode)
|
||||
{
|
||||
u32 num_props;
|
||||
struct ipa_eth_channel *ch;
|
||||
struct ipa_tx_intf *tx_intf = ð_dev->ipa_tx_intf;
|
||||
|
||||
if (tx_intf->prop) {
|
||||
ipa_eth_dev_bug(eth_dev, "IPA interface Tx prop is not empty");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* one each for IPv4 and IPv6 */
|
||||
num_props = __list_size(ð_dev->tx_channels) * 2;
|
||||
|
||||
tx_intf->prop = kcalloc(num_props, sizeof(*tx_intf->prop), GFP_KERNEL);
|
||||
if (!tx_intf->prop) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to alloc tx props");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
tx_intf->num_props = 0;
|
||||
list_for_each_entry(ch, ð_dev->tx_channels, channel_list) {
|
||||
ipa_eth_ep_init_tx_props(&tx_intf->prop[tx_intf->num_props++],
|
||||
ch, true, vlan_mode);
|
||||
|
||||
ipa_eth_ep_init_tx_props(&tx_intf->prop[tx_intf->num_props++],
|
||||
ch, false, vlan_mode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_init_rx_intf(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
bool vlan_mode)
|
||||
{
|
||||
u32 num_props;
|
||||
struct ipa_eth_channel *ch;
|
||||
struct ipa_rx_intf *rx_intf = ð_dev->ipa_rx_intf;
|
||||
|
||||
if (rx_intf->prop) {
|
||||
ipa_eth_dev_bug(eth_dev, "IPA interface Rx prop is not empty");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* one each for IPv4 and IPv6 */
|
||||
num_props = __list_size(ð_dev->rx_channels) * 2;
|
||||
|
||||
rx_intf->prop = kcalloc(num_props, sizeof(*rx_intf->prop), GFP_KERNEL);
|
||||
if (!rx_intf->prop) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to alloc rx props");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rx_intf->num_props = 0;
|
||||
list_for_each_entry(ch, ð_dev->rx_channels, channel_list) {
|
||||
ipa_eth_ep_init_rx_props(&rx_intf->prop[rx_intf->num_props++],
|
||||
ch, true, vlan_mode);
|
||||
|
||||
ipa_eth_ep_init_rx_props(&rx_intf->prop[rx_intf->num_props++],
|
||||
ch, false, vlan_mode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ipa_eth_ep_deinit_interfaces(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
kzfree(eth_dev->ipa_tx_intf.prop);
|
||||
memset(ð_dev->ipa_tx_intf, 0, sizeof(eth_dev->ipa_tx_intf));
|
||||
|
||||
kzfree(eth_dev->ipa_rx_intf.prop);
|
||||
memset(ð_dev->ipa_rx_intf, 0, sizeof(eth_dev->ipa_rx_intf));
|
||||
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_init_interfaces(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
bool vlan_mode;
|
||||
|
||||
if (eth_dev->ipa_rx_intf.num_props || eth_dev->ipa_tx_intf.num_props) {
|
||||
ipa_eth_dev_err(eth_dev, "Interface properties already exist");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
rc = ipa3_is_vlan_mode(IPA_VLAN_IF_ETH, &vlan_mode);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Could not determine IPA VLAN mode");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ipa_eth_ep_init_tx_intf(eth_dev, vlan_mode);
|
||||
if (rc)
|
||||
goto free_and_exit;
|
||||
|
||||
rc = ipa_eth_ep_init_rx_intf(eth_dev, vlan_mode);
|
||||
if (rc)
|
||||
goto free_and_exit;
|
||||
|
||||
return 0;
|
||||
|
||||
free_and_exit:
|
||||
ipa_eth_ep_deinit_interfaces(eth_dev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_deregister_ipa_intf(
|
||||
struct net_device *net_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
ipa_eth_log("Deregistering IPA intf %s", net_dev->name);
|
||||
|
||||
rc = ipa_deregister_intf(net_dev->name);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to deregister IPA intf %s", net_dev->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ipa_eth_send_msg_disconnect(net_dev);
|
||||
if (rc)
|
||||
ipa_eth_err("Failed to send disconnect message", net_dev->name);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_register_ipa_intf(
|
||||
struct net_device *net_dev,
|
||||
struct ipa_tx_intf *tx_intf,
|
||||
struct ipa_rx_intf *rx_intf)
|
||||
{
|
||||
int rc;
|
||||
|
||||
ipa_eth_log("Registering IPA intf %s", net_dev->name);
|
||||
|
||||
rc = ipa_register_intf(net_dev->name, tx_intf, rx_intf);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to register IPA intf %s", net_dev->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ipa_eth_send_msg_connect(net_dev);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to send connect message", net_dev->name);
|
||||
(void) ipa_eth_ep_deregister_ipa_intf(net_dev);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_register_alt_interface(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
struct net_device *net_dev)
|
||||
{
|
||||
struct ipa_tx_intf *tx_intf = ð_dev->ipa_tx_intf;
|
||||
struct ipa_rx_intf *rx_intf = ð_dev->ipa_rx_intf;
|
||||
|
||||
return ipa_eth_ep_register_ipa_intf(net_dev, tx_intf, rx_intf);
|
||||
}
|
||||
|
||||
static int ipa_eth_ep_deregister_alt_interface(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
struct net_device *net_dev)
|
||||
{
|
||||
return ipa_eth_ep_deregister_ipa_intf(net_dev);
|
||||
}
|
||||
|
||||
static int __ipa_eth_ep_register_interface(
|
||||
struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct net_device *net_dev = eth_dev->net_dev;
|
||||
struct ipa_tx_intf *tx_intf = ð_dev->ipa_tx_intf;
|
||||
struct ipa_rx_intf *rx_intf = ð_dev->ipa_rx_intf;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Registering interface %s", net_dev->name);
|
||||
|
||||
rc = ipa_eth_ep_register_ipa_intf(net_dev, tx_intf, rx_intf);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to register IPA interface");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_register_interface() - Set Rx and Tx properties and register the
|
||||
* interface with IPA
|
||||
* @eth_dev: Offload device to register
|
||||
*
|
||||
* Register a logical interface with IPA. The API expects netdev and channels
|
||||
* allocated prior to being called.
|
||||
*
|
||||
* Return: 0 on success, negative errno code otherwise
|
||||
*/
|
||||
int ipa_eth_ep_register_interface(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Registering interface with IPA driver");
|
||||
|
||||
rc = ipa_eth_ep_init_interfaces(eth_dev);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = __ipa_eth_ep_register_interface(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to register IPA interface");
|
||||
ipa_eth_ep_deinit_interfaces(eth_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_unregister_interface() - Unregister a previously registered
|
||||
* interface
|
||||
* @eth_dev: Offload device to unregister
|
||||
*/
|
||||
int ipa_eth_ep_unregister_interface(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Unregistering interface from IPA driver");
|
||||
|
||||
rc = ipa_eth_ep_deregister_ipa_intf(eth_dev->net_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to de-register one or more interfaces");
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_ep_deinit_interfaces(eth_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_ep_register_upper_interface(
|
||||
struct ipa_eth_upper_device *upper_eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct net_device *net_dev = upper_eth_dev->net_dev;
|
||||
struct ipa_eth_device *eth_dev = upper_eth_dev->eth_dev;
|
||||
|
||||
if (upper_eth_dev->registered)
|
||||
return 0;
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Registering upper interface %s", net_dev->name);
|
||||
|
||||
rc = ipa_eth_ep_register_alt_interface(eth_dev, net_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to register upper interface %s", net_dev->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
upper_eth_dev->registered = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_ep_unregister_upper_interface(
|
||||
struct ipa_eth_upper_device *upper_eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct net_device *net_dev = upper_eth_dev->net_dev;
|
||||
struct ipa_eth_device *eth_dev = upper_eth_dev->eth_dev;
|
||||
|
||||
if (!upper_eth_dev->registered)
|
||||
return 0;
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Unegistering upper interface %s", net_dev->name);
|
||||
|
||||
rc = ipa_eth_ep_deregister_alt_interface(eth_dev, net_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to unregister upper interface %s",
|
||||
net_dev->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
upper_eth_dev->registered = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_init_ctx - Initialize IPA endpoint context for a channel
|
||||
* @ch: Channel for which EP ctx need to be initialized
|
||||
* @vlan_mode: true if VLAN mode is enabled for the EP
|
||||
*/
|
||||
void ipa_eth_ep_init_ctx(struct ipa_eth_channel *ch, bool vlan_mode)
|
||||
{
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (ep_ctx->valid)
|
||||
return;
|
||||
|
||||
memset(ep_ctx, 0, offsetof(typeof(*ep_ctx), sys));
|
||||
|
||||
ep_ctx->valid = 1;
|
||||
ep_ctx->client = ch->ipa_client;
|
||||
ep_ctx->client_notify = ipa_ep_client_notifier;
|
||||
ep_ctx->priv = ch;
|
||||
|
||||
ep_ctx->cfg.nat.nat_en = IPA_CLIENT_IS_PROD(ch->ipa_client) ?
|
||||
IPA_SRC_NAT : IPA_BYPASS_NAT;
|
||||
ep_ctx->cfg.hdr.hdr_len = vlan_mode ? VLAN_ETH_HLEN : ETH_HLEN;
|
||||
ep_ctx->cfg.mode.mode = IPA_BASIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_deinit_ctx - Deinitialize IPA endpoint context for a channel
|
||||
* @ch: Channel for which EP ctx need to be deinitialized
|
||||
*/
|
||||
void ipa_eth_ep_deinit_ctx(struct ipa_eth_channel *ch)
|
||||
{
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid)
|
||||
return;
|
||||
|
||||
ep_ctx->valid = false;
|
||||
|
||||
memset(ep_ctx, 0, offsetof(typeof(*ep_ctx), sys));
|
||||
}
|
||||
|
||||
static int __ipa_eth_ep_init(struct ipa_eth_channel *ch,
|
||||
struct ipa3_ep_context *ep_ctx)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ipa3_cfg_ep(ch->ipa_ep_num, &ep_ctx->cfg);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to configure EP %d", ch->ipa_ep_num);
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
if (IPA_CLIENT_IS_PROD(ch->ipa_client))
|
||||
ipa3_install_dflt_flt_rules(ch->ipa_ep_num);
|
||||
|
||||
err_exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_init() - Initialize IPA endpoint for a channel
|
||||
* @ch: Channel for which EP need to be initialized
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_ep_init(struct ipa_eth_channel *ch)
|
||||
{
|
||||
int rc;
|
||||
struct ipa3_ep_context *ep_ctx;
|
||||
|
||||
ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_bug(ch->eth_dev, "EP context is not initialiazed");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
IPA_ACTIVE_CLIENTS_INC_SIMPLE();
|
||||
rc = __ipa_eth_ep_init(ch, ep_ctx);
|
||||
IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_ep_init);
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_deinit() - Deinitialize IPA endpoint for a channel
|
||||
* @ch: Channel for which EP need to be deinitialized
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_ep_deinit(struct ipa_eth_channel *ch)
|
||||
{
|
||||
IPA_ACTIVE_CLIENTS_INC_SIMPLE();
|
||||
|
||||
if (IPA_CLIENT_IS_PROD(ch->ipa_client))
|
||||
ipa3_delete_dflt_flt_rules(ch->ipa_ep_num);
|
||||
|
||||
IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_ep_deinit);
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_start() - Start an IPA endpoint
|
||||
* @ch: Channel for which the IPA EP need to be started
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_ep_start(struct ipa_eth_channel *ch)
|
||||
{
|
||||
int rc = ipa3_enable_data_path(ch->ipa_ep_num);
|
||||
|
||||
if (rc)
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to start EP %d", ch->ipa_ep_num);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_ep_start);
|
||||
|
||||
/**
|
||||
* ipa_eth_ep_stop() - Stop an IPA endpoint
|
||||
* @ch: Channel for which the IPA EP need to be stopped
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_ep_stop(struct ipa_eth_channel *ch)
|
||||
{
|
||||
int rc = ipa3_disable_data_path(ch->ipa_ep_num);
|
||||
|
||||
if (rc)
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to stop EP %d", ch->ipa_ep_num);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_ep_stop);
|
||||
371
drivers/platform/msm/ethernet/ipa_eth_gsi.c
Normal file
371
drivers/platform/msm/ethernet/ipa_eth_gsi.c
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/msm_gsi.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
static void ipa_eth_gsi_ev_err(struct gsi_evt_err_notify *notify)
|
||||
{
|
||||
struct ipa_eth_channel *ch = notify->user_data;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Error (id=%d, edesc=%04x) in GSI event ring %u",
|
||||
notify->evt_id, notify->err_desc,
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
}
|
||||
|
||||
static void ipa_eth_gsi_ch_err(struct gsi_chan_err_notify *notify)
|
||||
{
|
||||
struct ipa_eth_channel *ch = notify->chan_user_data;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Error (id=%d, edesc=%04x) in GSI channel %u",
|
||||
notify->evt_id, notify->err_desc,
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
}
|
||||
|
||||
/*
|
||||
* ipa_eth_gsi_alloc() - Allocate GSI channel and event ring for an offload
|
||||
* channel, optionally writing to the ring scratch
|
||||
* register and fetch the ring doorbell address
|
||||
* @ch: Offload channel
|
||||
* @gsi_ev_props: Properties of the GSI event ring to be allocated
|
||||
* @gsi_ev_scratch: Optional. Points to the value to be written to GSI
|
||||
* event ring scratch register
|
||||
* @gsi_ev_db: Optional. Writes event ring doorbell LSB address to the location
|
||||
* pointed to by the argument
|
||||
* @gsi_ch_props: Properties of the GSI channel to be allocated
|
||||
* @gsi_ch_scratch: Optional. Points to the value to be written to GSI
|
||||
* event ring scratch register
|
||||
* @gsi_ch_db: Optional. Writes channel doorbell LSB address to the location
|
||||
* pointed to by the argument
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_gsi_alloc(struct ipa_eth_channel *ch,
|
||||
struct gsi_evt_ring_props *gsi_ev_props,
|
||||
union gsi_evt_scratch *gsi_ev_scratch,
|
||||
phys_addr_t *gsi_ev_db,
|
||||
struct gsi_chan_props *gsi_ch_props,
|
||||
union gsi_channel_scratch *gsi_ch_scratch,
|
||||
phys_addr_t *gsi_ch_db)
|
||||
{
|
||||
enum gsi_status gsi_rc = GSI_STATUS_SUCCESS;
|
||||
const struct ipa_gsi_ep_config *gsi_ep_cfg;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "EP context is not initialized");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
gsi_ep_cfg = ipa3_get_gsi_ep_info(ep_ctx->client);
|
||||
if (!gsi_ep_cfg) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "Failed to obtain GSI EP info");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!gsi_ev_props->err_cb) {
|
||||
gsi_ev_props->err_cb = ipa_eth_gsi_ev_err;
|
||||
gsi_ev_props->user_data = ch;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_alloc_evt_ring(gsi_ev_props, ipa3_ctx->gsi_dev_hdl,
|
||||
&ep_ctx->gsi_evt_ring_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "Failed to alloc GSI event ring");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ipa_eth_dev_dbg(ch->eth_dev, "GSI event ring handle is %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
|
||||
if (gsi_ev_db) {
|
||||
u32 db_addr_lsb = 0;
|
||||
u32 db_addr_msb = 0;
|
||||
|
||||
gsi_rc = gsi_query_evt_ring_db_addr(ep_ctx->gsi_evt_ring_hdl,
|
||||
&db_addr_lsb, &db_addr_msb);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to get DB address for event ring %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
goto err_free_ev;
|
||||
}
|
||||
|
||||
ipa_eth_dev_dbg(ch->eth_dev,
|
||||
"GSI event ring %lu DB address LSB is 0x%08x",
|
||||
ep_ctx->gsi_evt_ring_hdl, db_addr_lsb);
|
||||
ipa_eth_dev_dbg(ch->eth_dev,
|
||||
"GSI event ring %lu DB address MSB is 0x%08x",
|
||||
ep_ctx->gsi_evt_ring_hdl, db_addr_msb);
|
||||
|
||||
*gsi_ev_db = db_addr_lsb;
|
||||
}
|
||||
|
||||
if (gsi_ev_scratch) {
|
||||
gsi_rc = gsi_write_evt_ring_scratch(ep_ctx->gsi_evt_ring_hdl,
|
||||
*gsi_ev_scratch);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to write scratch for event ring %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
goto err_free_ev;
|
||||
}
|
||||
}
|
||||
|
||||
gsi_ch_props->ch_id = gsi_ep_cfg->ipa_gsi_chan_num;
|
||||
gsi_ch_props->evt_ring_hdl = ep_ctx->gsi_evt_ring_hdl;
|
||||
|
||||
gsi_ch_props->prefetch_mode = gsi_ep_cfg->prefetch_mode;
|
||||
gsi_ch_props->empty_lvl_threshold = gsi_ep_cfg->prefetch_threshold;
|
||||
|
||||
if (!gsi_ch_props->err_cb) {
|
||||
gsi_ch_props->err_cb = ipa_eth_gsi_ch_err;
|
||||
gsi_ch_props->chan_user_data = ch;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_alloc_channel(gsi_ch_props, ipa3_ctx->gsi_dev_hdl,
|
||||
&ep_ctx->gsi_chan_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "Failed to alloc GSI channel");
|
||||
goto err_free_ev;
|
||||
}
|
||||
|
||||
ipa_eth_dev_dbg(ch->eth_dev, "GSI channel handle is %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
|
||||
if (gsi_ch_db) {
|
||||
u32 db_addr_lsb = 0;
|
||||
u32 db_addr_msb = 0;
|
||||
|
||||
gsi_rc = gsi_query_channel_db_addr(ep_ctx->gsi_chan_hdl,
|
||||
&db_addr_lsb, &db_addr_msb);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to get DB address for channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
goto err_free_ch;
|
||||
}
|
||||
|
||||
ipa_eth_dev_dbg(ch->eth_dev,
|
||||
"GSI channel %lu DB address LSB is 0x%08x",
|
||||
ep_ctx->gsi_chan_hdl, db_addr_lsb);
|
||||
ipa_eth_dev_dbg(ch->eth_dev,
|
||||
"GSI channel %lu DB address MSB is 0x%08x",
|
||||
ep_ctx->gsi_chan_hdl, db_addr_msb);
|
||||
|
||||
*gsi_ch_db = db_addr_lsb;
|
||||
}
|
||||
|
||||
if (gsi_ch_scratch) {
|
||||
gsi_rc = gsi_write_channel_scratch(ep_ctx->gsi_chan_hdl,
|
||||
*gsi_ch_scratch);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to write scratch for channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
goto err_free_ch;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_ch:
|
||||
if (gsi_dealloc_channel(ep_ctx->gsi_chan_hdl))
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to dealloc GSI channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
|
||||
ep_ctx->gsi_chan_hdl = ~0;
|
||||
|
||||
err_free_ev:
|
||||
if (gsi_dealloc_evt_ring(ep_ctx->gsi_evt_ring_hdl))
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to dealloc GSI event ring %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
|
||||
ep_ctx->gsi_evt_ring_hdl = ~0;
|
||||
|
||||
return gsi_rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_gsi_alloc);
|
||||
|
||||
/**
|
||||
* ipa_eth_gsi_dealloc() - De-allocate GSI event ring and channel associated
|
||||
* with an offload channel, previously allocated with
|
||||
* ipa_eth_gsi_alloc()
|
||||
* @ch: Offload channel
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_gsi_dealloc(struct ipa_eth_channel *ch)
|
||||
{
|
||||
enum gsi_status gsi_rc = GSI_STATUS_SUCCESS;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "EP context is not initialized");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (ep_ctx->gsi_chan_hdl != ~0) {
|
||||
gsi_rc = gsi_reset_channel(ep_ctx->gsi_chan_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to reset channel %u",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_dealloc_channel(ep_ctx->gsi_chan_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to dealloc channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
ep_ctx->gsi_chan_hdl = ~0;
|
||||
}
|
||||
|
||||
if (ep_ctx->gsi_evt_ring_hdl != ~0) {
|
||||
gsi_rc = gsi_reset_evt_ring(ep_ctx->gsi_evt_ring_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to reset event ring %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_dealloc_evt_ring(ep_ctx->gsi_evt_ring_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to dealloc event ring %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
ep_ctx->gsi_evt_ring_hdl = ~0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_gsi_dealloc);
|
||||
|
||||
/*
|
||||
* ipa_eth_gsi_ring_evtring() - Ring an offload channel event ring doorbell
|
||||
* @ch: Offload channel associated with the event ring
|
||||
* @value: Value to write to the doorbell
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_gsi_ring_evtring(struct ipa_eth_channel *ch, u64 value)
|
||||
{
|
||||
enum gsi_status gsi_rc = GSI_STATUS_SUCCESS;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "EP context is not initialized");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_ring_evt_ring_db(ep_ctx->gsi_evt_ring_hdl, value);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to ring DB for event ring %lu",
|
||||
ep_ctx->gsi_evt_ring_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_gsi_ring_evtring);
|
||||
|
||||
/*
|
||||
* ipa_eth_gsi_ring_channel() - Ring an offload channel GSI channel doorbell
|
||||
* @ch: Offload channel associated with the GSI channel
|
||||
* @value: Value to write to the doorbell
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_gsi_ring_channel(struct ipa_eth_channel *ch, u64 value)
|
||||
{
|
||||
enum gsi_status gsi_rc = GSI_STATUS_SUCCESS;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "EP context is not initialized");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_ring_ch_ring_db(ep_ctx->gsi_chan_hdl, value);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to ring DB for channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_gsi_ring_channel);
|
||||
|
||||
/**
|
||||
* ipa_eth_gsi_start() - Start GSI channel associated with offload channel
|
||||
* @ch: Offload channel
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_gsi_start(struct ipa_eth_channel *ch)
|
||||
{
|
||||
enum gsi_status gsi_rc = GSI_STATUS_SUCCESS;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "EP context is not initialized");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_start_channel(ep_ctx->gsi_chan_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "Failed to start GSI channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_gsi_start);
|
||||
|
||||
/**
|
||||
* ipa_eth_gsi_stop() - Stop GSI channel associated with offload channel
|
||||
* @ch: Offload channel
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_gsi_stop(struct ipa_eth_channel *ch)
|
||||
{
|
||||
enum gsi_status gsi_rc = GSI_STATUS_SUCCESS;
|
||||
struct ipa3_ep_context *ep_ctx = &ipa3_ctx->ep[ch->ipa_ep_num];
|
||||
|
||||
if (!ep_ctx->valid) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "EP context is not initialized");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
gsi_rc = gsi_stop_channel(ep_ctx->gsi_chan_hdl);
|
||||
if (gsi_rc != GSI_STATUS_SUCCESS) {
|
||||
ipa_eth_dev_err(ch->eth_dev, "Failed to stop GSI channel %lu",
|
||||
ep_ctx->gsi_chan_hdl);
|
||||
return gsi_rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_gsi_stop);
|
||||
272
drivers/platform/msm/ethernet/ipa_eth_i.h
Normal file
272
drivers/platform/msm/ethernet/ipa_eth_i.h
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef _IPA_ETH_I_H_
|
||||
#define _IPA_ETH_I_H_
|
||||
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#define IPA_ETH_NET_DRIVER
|
||||
#define IPA_ETH_OFFLOAD_DRIVER
|
||||
#include <linux/ipa_eth.h>
|
||||
|
||||
#include "ipa_eth_debugfs.h"
|
||||
|
||||
#include "../ipa_i.h"
|
||||
|
||||
/* Time to remain awake after a suspend abort due to NIC activity */
|
||||
#define IPA_ETH_WAKE_TIME_MS 500
|
||||
|
||||
/* Time for NIC HW to settle down (ex. receive link interrupt) after a resume */
|
||||
#define IPA_ETH_RESUME_SETTLE_MS 2000
|
||||
|
||||
#define IPA_ETH_PFDEV (ipa3_ctx ? ipa3_ctx->pdev : NULL)
|
||||
#define IPA_ETH_SUBSYS "ipa_eth"
|
||||
|
||||
#define IPA_ETH_NET_DEVICE_MAX_EVENTS (NETDEV_CHANGE_TX_QUEUE_LEN + 1)
|
||||
#define IPA_ETH_PM_NOTIFIER_MAX_EVENTS (PM_POST_RESTORE + 1)
|
||||
|
||||
enum ipa_eth_states {
|
||||
IPA_ETH_ST_READY,
|
||||
IPA_ETH_ST_API_READY,
|
||||
IPA_ETH_ST_MAX,
|
||||
};
|
||||
|
||||
enum ipa_eth_dev_flags {
|
||||
IPA_ETH_DEV_F_REMOVING,
|
||||
IPA_ETH_DEV_F_UNPAIRING,
|
||||
IPA_ETH_DEV_F_RESETTING,
|
||||
IPA_ETH_DEV_F_COUNT,
|
||||
};
|
||||
|
||||
#define ipa_eth_err(fmt, args...) \
|
||||
do { \
|
||||
dev_err(IPA_ETH_PFDEV, \
|
||||
IPA_ETH_SUBSYS " %s:%d ERROR: " fmt "\n", \
|
||||
__func__, __LINE__, ## args); \
|
||||
ipa_eth_ipc_log("ERROR: " fmt, ## args); \
|
||||
} while (0)
|
||||
|
||||
#define ipa_eth_bug(fmt, args...) \
|
||||
do { \
|
||||
ipa_eth_err("BUG: " fmt, ## args); \
|
||||
dump_stack(); \
|
||||
} while (0)
|
||||
|
||||
#define ipa_eth_log(fmt, args...) \
|
||||
do { \
|
||||
dev_dbg(IPA_ETH_PFDEV, \
|
||||
IPA_ETH_SUBSYS " %s:%d " fmt "\n", \
|
||||
__func__, __LINE__, ## args); \
|
||||
ipa_eth_ipc_log(fmt, ## args); \
|
||||
} while (0)
|
||||
|
||||
#define ipa_eth_dbg(fmt, args...) \
|
||||
do { \
|
||||
dev_dbg(IPA_ETH_PFDEV, \
|
||||
IPA_ETH_SUBSYS " %s:%d " fmt "\n", \
|
||||
__func__, __LINE__, ## args); \
|
||||
ipa_eth_ipc_dbg("DEBUG: " fmt, ## args); \
|
||||
} while (0)
|
||||
|
||||
#define ipa_eth_dev_err(edev, fmt, args...) \
|
||||
ipa_eth_err("(%s) " fmt, ((edev && edev->net_dev) ? \
|
||||
edev->net_dev->name : "<unpaired>"), \
|
||||
## args)
|
||||
|
||||
#define ipa_eth_dev_bug(edev, fmt, args...) \
|
||||
ipa_eth_bug("(%s) " fmt, ((edev && edev->net_dev) ? \
|
||||
edev->net_dev->name : "<unpaired>"), \
|
||||
## args)
|
||||
|
||||
#define ipa_eth_dev_dbg(edev, fmt, args...) \
|
||||
ipa_eth_dbg("(%s) " fmt, ((edev && edev->net_dev) ? \
|
||||
edev->net_dev->name : "<unpaired>"), \
|
||||
## args)
|
||||
|
||||
#define ipa_eth_dev_log(edev, fmt, args...) \
|
||||
ipa_eth_log("(%s) " fmt, ((edev && edev->net_dev) ? \
|
||||
edev->net_dev->name : "<unpaired>"), \
|
||||
## args)
|
||||
|
||||
struct ipa_eth_upper_device {
|
||||
struct list_head upper_list;
|
||||
|
||||
struct net_device *net_dev;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
bool up; /* interface is up */
|
||||
bool registered; /* registered with IPA */
|
||||
};
|
||||
|
||||
struct ipa_eth_device_private {
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
struct list_head upper_devices;
|
||||
|
||||
unsigned long assume_active;
|
||||
struct rtnl_link_stats64 last_rtnl_stats;
|
||||
|
||||
struct notifier_block pm_nb;
|
||||
struct notifier_block panic_nb;
|
||||
};
|
||||
|
||||
#define eth_dev_priv(eth_dev) \
|
||||
((struct ipa_eth_device_private *)((eth_dev)->ipa_priv))
|
||||
|
||||
struct ipa_eth_bus {
|
||||
struct list_head bus_list;
|
||||
|
||||
struct bus_type *bus;
|
||||
|
||||
int (*register_driver)(struct ipa_eth_net_driver *nd);
|
||||
void (*unregister_driver)(struct ipa_eth_net_driver *nd);
|
||||
|
||||
int (*enable_pc)(struct ipa_eth_device *eth_dev);
|
||||
int (*disable_pc)(struct ipa_eth_device *eth_dev);
|
||||
};
|
||||
|
||||
extern struct ipa_eth_bus ipa_eth_pci_bus;
|
||||
|
||||
struct ipa_eth_cb_map_param {
|
||||
bool map;
|
||||
bool sym;
|
||||
int iommu_prot;
|
||||
enum dma_data_direction dma_dir;
|
||||
const struct ipa_smmu_cb_ctx *cb_ctx;
|
||||
};
|
||||
|
||||
extern unsigned long ipa_eth_state;
|
||||
extern bool ipa_eth_noauto;
|
||||
extern bool ipa_eth_ipc_logdbg;
|
||||
|
||||
bool ipa_eth_is_ready(void);
|
||||
bool ipa_eth_all_ready(void);
|
||||
|
||||
static inline void ipa_eth_dev_assume_active_ms(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
unsigned int msec)
|
||||
{
|
||||
eth_dev_priv(eth_dev)->assume_active +=
|
||||
DIV_ROUND_UP(msec, IPA_ETH_WAKE_TIME_MS);
|
||||
pm_system_wakeup();
|
||||
}
|
||||
|
||||
static inline void ipa_eth_dev_assume_active_inc(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
unsigned int count)
|
||||
{
|
||||
eth_dev_priv(eth_dev)->assume_active += count;
|
||||
pm_system_wakeup();
|
||||
}
|
||||
|
||||
static inline void ipa_eth_dev_assume_active_dec(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
unsigned int count)
|
||||
{
|
||||
if (eth_dev_priv(eth_dev)->assume_active > count)
|
||||
eth_dev_priv(eth_dev)->assume_active -= count;
|
||||
else
|
||||
eth_dev_priv(eth_dev)->assume_active = 0;
|
||||
}
|
||||
|
||||
struct ipa_eth_device *ipa_eth_alloc_device(
|
||||
struct device *dev,
|
||||
struct ipa_eth_net_driver *nd);
|
||||
void ipa_eth_free_device(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_register_device(struct ipa_eth_device *eth_dev);
|
||||
void ipa_eth_unregister_device(struct ipa_eth_device *eth_dev);
|
||||
|
||||
void ipa_eth_device_refresh_sync(struct ipa_eth_device *eth_dev);
|
||||
void ipa_eth_device_refresh_sched(struct ipa_eth_device *eth_dev);
|
||||
void ipa_eth_global_refresh_sched(void);
|
||||
|
||||
int ipa_eth_pci_modinit(void);
|
||||
void ipa_eth_pci_modexit(void);
|
||||
|
||||
int ipa_eth_bus_modinit(void);
|
||||
void ipa_eth_bus_modexit(void);
|
||||
|
||||
int ipa_eth_bus_register_driver(struct ipa_eth_net_driver *nd);
|
||||
void ipa_eth_bus_unregister_driver(struct ipa_eth_net_driver *nd);
|
||||
|
||||
int ipa_eth_bus_enable_pc(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_bus_disable_pc(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_offload_register_driver(struct ipa_eth_offload_driver *od);
|
||||
void ipa_eth_offload_unregister_driver(struct ipa_eth_offload_driver *od);
|
||||
|
||||
static inline bool ipa_eth_offload_device_paired(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return eth_dev->od != NULL;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_pair_device(struct ipa_eth_device *eth_dev);
|
||||
void ipa_eth_offload_unpair_device(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_offload_init(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_offload_deinit(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_offload_start(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_offload_stop(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_offload_save_regs(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_offload_prepare_reset(struct ipa_eth_device *eth_dev, void *data);
|
||||
int ipa_eth_offload_complete_reset(struct ipa_eth_device *eth_dev, void *data);
|
||||
|
||||
bool ipa_eth_net_check_active(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_net_register_driver(struct ipa_eth_net_driver *nd);
|
||||
void ipa_eth_net_unregister_driver(struct ipa_eth_net_driver *nd);
|
||||
int ipa_eth_net_register_upper(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_net_unregister_upper(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_net_open_device(struct ipa_eth_device *eth_dev);
|
||||
void ipa_eth_net_close_device(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_net_save_regs(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_ep_init_headers(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_ep_deinit_headers(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_ep_register_interface(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_ep_unregister_interface(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_ep_register_upper_interface(
|
||||
struct ipa_eth_upper_device *upper_eth_dev);
|
||||
int ipa_eth_ep_unregister_upper_interface(
|
||||
struct ipa_eth_upper_device *upper_eth_dev);
|
||||
|
||||
void ipa_eth_ep_init_ctx(struct ipa_eth_channel *ch, bool vlan_mode);
|
||||
void ipa_eth_ep_deinit_ctx(struct ipa_eth_channel *ch);
|
||||
|
||||
int ipa_eth_pm_register(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_pm_unregister(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_pm_activate(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_pm_deactivate(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_pm_vote_bw(struct ipa_eth_device *eth_dev);
|
||||
|
||||
int ipa_eth_uc_stats_init(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_uc_stats_deinit(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_uc_stats_start(struct ipa_eth_device *eth_dev);
|
||||
int ipa_eth_uc_stats_stop(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/* ipa_eth_utils.c APIs */
|
||||
|
||||
const char *ipa_eth_device_event_name(enum ipa_eth_device_event event);
|
||||
const char *ipa_eth_net_device_event_name(unsigned long event);
|
||||
const char *ipa_eth_pm_notifier_event_name(unsigned long event);
|
||||
|
||||
int ipa_eth_send_msg_connect(struct net_device *net_dev);
|
||||
int ipa_eth_send_msg_disconnect(struct net_device *net_dev);
|
||||
|
||||
void *ipa_eth_get_ipc_logbuf(void);
|
||||
void *ipa_eth_get_ipc_logbuf_dbg(void);
|
||||
|
||||
int ipa_eth_ipc_log_init(void);
|
||||
void ipa_eth_ipc_log_cleanup(void);
|
||||
|
||||
#endif // _IPA_ETH_I_H_
|
||||
1551
drivers/platform/msm/ethernet/ipa_eth_net.c
Normal file
1551
drivers/platform/msm/ethernet/ipa_eth_net.c
Normal file
File diff suppressed because it is too large
Load diff
260
drivers/platform/msm/ethernet/ipa_eth_offload.c
Normal file
260
drivers/platform/msm/ethernet/ipa_eth_offload.c
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
static LIST_HEAD(ipa_eth_offload_drivers);
|
||||
static DEFINE_MUTEX(ipa_eth_offload_drivers_lock);
|
||||
|
||||
int ipa_eth_offload_init(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = eth_dev->od->ops->init_tx(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to init offload for tx");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = eth_dev->od->ops->init_rx(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to init offload for rx");
|
||||
(void) eth_dev->od->ops->deinit_tx(eth_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_deinit(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc_rx, rc_tx;
|
||||
|
||||
rc_rx = eth_dev->od->ops->deinit_rx(eth_dev);
|
||||
if (rc_rx)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to deinit offload for rx");
|
||||
|
||||
rc_tx = eth_dev->od->ops->deinit_tx(eth_dev);
|
||||
if (rc_tx)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to deinit offload for tx");
|
||||
|
||||
return rc_rx || rc_tx;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_start(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = eth_dev->od->ops->start_tx(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to start offload for tx");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = eth_dev->od->ops->start_rx(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to start offload for rx");
|
||||
(void) eth_dev->od->ops->stop_tx(eth_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_stop(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc_rx, rc_tx;
|
||||
|
||||
rc_rx = eth_dev->od->ops->stop_rx(eth_dev);
|
||||
if (rc_rx)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to stop offload for rx");
|
||||
|
||||
rc_tx = eth_dev->od->ops->stop_tx(eth_dev);
|
||||
if (rc_tx)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to stop offload for tx");
|
||||
|
||||
return rc_rx || rc_tx;
|
||||
}
|
||||
|
||||
static int __try_pair_device(struct ipa_eth_device *eth_dev,
|
||||
struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
int rc = od->ops->pair(eth_dev);
|
||||
|
||||
if (rc) {
|
||||
ipa_eth_dev_dbg(eth_dev,
|
||||
"Offload driver %s passed up paring with %s",
|
||||
od->name, eth_dev->nd->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Offload driver %s is paired with device from %s",
|
||||
od->name, eth_dev->nd->name);
|
||||
|
||||
eth_dev->od = od;
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Offload driver %s successfully paired with device from %s",
|
||||
od->name, eth_dev->nd->name);
|
||||
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int try_pair_device(struct ipa_eth_device *eth_dev,
|
||||
struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_lock(&od->mutex);
|
||||
|
||||
if (od->bus != eth_dev->dev->bus) {
|
||||
rc = -EOPNOTSUPP;
|
||||
ipa_eth_dev_dbg(eth_dev,
|
||||
"Offload driver %s is not a bus match for %s",
|
||||
od->name, eth_dev->nd->name);
|
||||
} else {
|
||||
rc = __try_pair_device(eth_dev, od);
|
||||
}
|
||||
|
||||
mutex_unlock(&od->mutex);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_pair_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc = -ENODEV;
|
||||
|
||||
mutex_lock(&ipa_eth_offload_drivers_lock);
|
||||
|
||||
/* Check if device is already paired while holding the offload_drivers
|
||||
* mutex to ensure this API is re-entrant safe.
|
||||
*/
|
||||
if (!eth_dev->od) {
|
||||
struct ipa_eth_offload_driver *od;
|
||||
|
||||
list_for_each_entry(od, &ipa_eth_offload_drivers, driver_list) {
|
||||
if (!try_pair_device(eth_dev, od)) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&ipa_eth_offload_drivers_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void ipa_eth_offload_unpair_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
mutex_lock(&ipa_eth_offload_drivers_lock);
|
||||
|
||||
/* Check if device is already unpaired while holding the offload_drivers
|
||||
* mutex to ensure this API is re-entrant safe.
|
||||
*/
|
||||
if (eth_dev->od) {
|
||||
struct ipa_eth_offload_driver *od = eth_dev->od;
|
||||
|
||||
mutex_lock(&od->mutex);
|
||||
od->ops->unpair(eth_dev);
|
||||
eth_dev->od = NULL;
|
||||
mutex_unlock(&od->mutex);
|
||||
}
|
||||
|
||||
mutex_unlock(&ipa_eth_offload_drivers_lock);
|
||||
}
|
||||
|
||||
static bool ipa_eth_offload_check_ops(struct ipa_eth_offload_ops *ops)
|
||||
{
|
||||
/* Following call backs are optional:
|
||||
* - get_stats()
|
||||
* - clear_stats()
|
||||
* - save_regs()
|
||||
* - prepare_reset()
|
||||
* - complete_reset()
|
||||
*/
|
||||
return
|
||||
ops &&
|
||||
ops->pair &&
|
||||
ops->unpair &&
|
||||
ops->init_tx &&
|
||||
ops->start_tx &&
|
||||
ops->stop_tx &&
|
||||
ops->deinit_tx &&
|
||||
ops->init_rx &&
|
||||
ops->start_rx &&
|
||||
ops->stop_rx &&
|
||||
ops->deinit_rx;
|
||||
}
|
||||
|
||||
static bool ipa_eth_offload_check_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
return
|
||||
od &&
|
||||
od->bus &&
|
||||
od->name &&
|
||||
ipa_eth_offload_check_ops(od->ops);
|
||||
}
|
||||
|
||||
int ipa_eth_offload_register_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
if (!ipa_eth_offload_check_driver(od)) {
|
||||
ipa_eth_err("Offload driver validation failed");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&od->mutex);
|
||||
|
||||
mutex_lock(&ipa_eth_offload_drivers_lock);
|
||||
list_add(&od->driver_list, &ipa_eth_offload_drivers);
|
||||
mutex_unlock(&ipa_eth_offload_drivers_lock);
|
||||
|
||||
(void) ipa_eth_debugfs_add_offload_driver(od);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ipa_eth_offload_unregister_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
ipa_eth_debugfs_remove_offload_driver(od);
|
||||
|
||||
mutex_lock(&ipa_eth_offload_drivers_lock);
|
||||
list_del(&od->driver_list);
|
||||
mutex_unlock(&ipa_eth_offload_drivers_lock);
|
||||
}
|
||||
|
||||
int ipa_eth_offload_save_regs(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
struct ipa_eth_offload_driver *od = eth_dev->od;
|
||||
|
||||
if (od && od->ops->save_regs)
|
||||
return eth_dev->od->ops->save_regs(eth_dev, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_prepare_reset(struct ipa_eth_device *eth_dev, void *data)
|
||||
{
|
||||
struct ipa_eth_offload_driver *od = eth_dev->od;
|
||||
|
||||
if (od && od->ops->prepare_reset)
|
||||
return eth_dev->od->ops->prepare_reset(eth_dev, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_offload_complete_reset(struct ipa_eth_device *eth_dev, void *data)
|
||||
{
|
||||
struct ipa_eth_offload_driver *od = eth_dev->od;
|
||||
|
||||
if (od && od->ops->complete_reset)
|
||||
return eth_dev->od->ops->complete_reset(eth_dev, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
612
drivers/platform/msm/ethernet/ipa_eth_pci.c
Normal file
612
drivers/platform/msm/ethernet/ipa_eth_pci.c
Normal file
|
|
@ -0,0 +1,612 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include <linux/msm_pcie.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
struct ipa_eth_pci_driver {
|
||||
struct ipa_eth_net_driver *nd;
|
||||
|
||||
int (*probe_real)(struct pci_dev *dev, const struct pci_device_id *id);
|
||||
void (*remove_real)(struct pci_dev *dev);
|
||||
|
||||
const struct dev_pm_ops *pm_ops_real;
|
||||
};
|
||||
|
||||
struct ipa_eth_pci_device {
|
||||
struct ipa_eth_device *eth_dev;
|
||||
struct ipa_eth_pci_driver *eth_pdrv;
|
||||
struct msm_pcie_register_event pcie_event;
|
||||
};
|
||||
|
||||
#define eth_dev_to_pdev(e) \
|
||||
((struct ipa_eth_pci_device *)e->bus_priv)
|
||||
|
||||
#define eth_dev_pm_ops(e) \
|
||||
(eth_dev_to_pdev(e)->eth_pdrv->pm_ops_real)
|
||||
|
||||
static LIST_HEAD(ipa_eth_pci_drivers);
|
||||
static DEFINE_MUTEX(ipa_eth_pci_drivers_mutex);
|
||||
|
||||
static LIST_HEAD(ipa_eth_pci_devices);
|
||||
static DEFINE_MUTEX(ipa_eth_pci_devices_mutex);
|
||||
|
||||
static bool ipa_eth_pci_is_ready;
|
||||
|
||||
static struct ipa_eth_pci_driver *__lookup_eth_pdrv(
|
||||
struct pci_driver *pdrv)
|
||||
{
|
||||
struct ipa_eth_net_driver *nd;
|
||||
|
||||
list_for_each_entry(nd, &ipa_eth_pci_drivers, bus_driver_list) {
|
||||
if (nd->driver == &pdrv->driver)
|
||||
return nd->bus_priv;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct ipa_eth_pci_driver *lookup_eth_pdrv(struct pci_driver *pdrv)
|
||||
{
|
||||
struct ipa_eth_pci_driver *eth_pdrv;
|
||||
|
||||
mutex_lock(&ipa_eth_pci_drivers_mutex);
|
||||
eth_pdrv = __lookup_eth_pdrv(pdrv);
|
||||
mutex_unlock(&ipa_eth_pci_drivers_mutex);
|
||||
|
||||
return eth_pdrv;
|
||||
}
|
||||
|
||||
static struct ipa_eth_device *__lookup_eth_dev(struct pci_dev *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
list_for_each_entry(eth_dev, &ipa_eth_pci_devices, bus_device_list) {
|
||||
if (eth_dev->dev == dev)
|
||||
return eth_dev;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct ipa_eth_device *lookup_eth_dev(struct pci_dev *pdev)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
mutex_lock(&ipa_eth_pci_devices_mutex);
|
||||
eth_dev = __lookup_eth_dev(pdev);
|
||||
mutex_unlock(&ipa_eth_pci_devices_mutex);
|
||||
|
||||
return eth_dev;
|
||||
}
|
||||
|
||||
static struct ipa_eth_device *dev_to_eth_dev(struct device *dev)
|
||||
{
|
||||
return lookup_eth_dev(to_pci_dev(dev));
|
||||
}
|
||||
|
||||
static bool is_driver_used(struct ipa_eth_pci_driver *eth_pdrv)
|
||||
{
|
||||
bool in_use = false;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
list_for_each_entry(eth_dev, &ipa_eth_pci_devices, bus_device_list) {
|
||||
if (eth_dev_to_pdev(eth_dev)->eth_pdrv == eth_pdrv) {
|
||||
in_use = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return in_use;
|
||||
}
|
||||
|
||||
static void ipa_eth_pcie_event_wakeup(struct pci_dev *pdev)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
/* We are not expected to be re-entrant while processing wake up
|
||||
* event.
|
||||
*/
|
||||
eth_dev = __lookup_eth_dev(pdev);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_bug("Failed to lookup eth device");
|
||||
return;
|
||||
}
|
||||
|
||||
if (eth_dev->start_on_wakeup && !eth_dev->start) {
|
||||
eth_dev->start = true;
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void ipa_eth_pcie_event_cb(struct msm_pcie_notify *notify)
|
||||
{
|
||||
struct pci_dev *pdev = notify->user;
|
||||
|
||||
ipa_eth_log("Received PCIe event %d", notify->event);
|
||||
|
||||
switch (notify->event) {
|
||||
case MSM_PCIE_EVENT_WAKEUP:
|
||||
ipa_eth_pcie_event_wakeup(pdev);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct ipa_eth_device *ipa_eth_pci_alloc_device(
|
||||
struct pci_dev *pdev,
|
||||
struct ipa_eth_pci_driver *eth_pdrv)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
struct ipa_eth_pci_device *eth_pdev;
|
||||
|
||||
eth_pdev = kzalloc(sizeof(*eth_pdev), GFP_KERNEL);
|
||||
if (!eth_pdev)
|
||||
return NULL;
|
||||
|
||||
eth_dev = ipa_eth_alloc_device(dev, eth_pdrv->nd);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_err("Failed to alloc eth device");
|
||||
kfree(eth_pdev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
eth_pdev->eth_dev = eth_dev;
|
||||
eth_pdev->eth_pdrv = eth_pdrv;
|
||||
|
||||
eth_pdev->pcie_event.events = MSM_PCIE_EVENT_WAKEUP;
|
||||
eth_pdev->pcie_event.user = pdev;
|
||||
eth_pdev->pcie_event.mode = MSM_PCIE_TRIGGER_CALLBACK;
|
||||
eth_pdev->pcie_event.callback = ipa_eth_pcie_event_cb;
|
||||
|
||||
eth_dev->bus_priv = eth_pdev;
|
||||
|
||||
return eth_dev;
|
||||
}
|
||||
|
||||
static void ipa_eth_pci_free_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
struct device *dev = eth_dev->dev;
|
||||
struct ipa_eth_pci_device *eth_pdev = eth_dev->bus_priv;
|
||||
|
||||
if (eth_pdev) {
|
||||
memset(eth_pdev, 0, sizeof(*eth_pdev));
|
||||
kfree(eth_pdev);
|
||||
}
|
||||
|
||||
ipa_eth_free_device(eth_dev);
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_register_device(
|
||||
struct pci_dev *pdev,
|
||||
struct ipa_eth_pci_driver *eth_pdrv)
|
||||
{
|
||||
int rc;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
eth_dev = ipa_eth_pci_alloc_device(pdev, eth_pdrv);
|
||||
if (!eth_dev) {
|
||||
rc = -ENOMEM;
|
||||
ipa_eth_err("Failed to alloc eth pci device");
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
rc = ipa_eth_register_device(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to register eth device");
|
||||
goto err_register;
|
||||
}
|
||||
|
||||
mutex_lock(&ipa_eth_pci_devices_mutex);
|
||||
list_add(ð_dev->bus_device_list, &ipa_eth_pci_devices);
|
||||
mutex_unlock(&ipa_eth_pci_devices_mutex);
|
||||
|
||||
rc = msm_pcie_register_event(ð_dev_to_pdev(eth_dev)->pcie_event);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to register for PCIe event");
|
||||
goto err_pcie_register;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_pcie_register:
|
||||
ipa_eth_unregister_device(eth_dev);
|
||||
err_register:
|
||||
ipa_eth_pci_free_device(eth_dev);
|
||||
err_alloc:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ipa_eth_pci_unregister_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
/* Deregister event handler first to prevent possible race with
|
||||
* __lookup_eth_dev() while remove the device from devices list.
|
||||
*/
|
||||
msm_pcie_deregister_event(ð_dev_to_pdev(eth_dev)->pcie_event);
|
||||
|
||||
mutex_lock(&ipa_eth_pci_devices_mutex);
|
||||
list_del(ð_dev->bus_device_list);
|
||||
mutex_unlock(&ipa_eth_pci_devices_mutex);
|
||||
|
||||
ipa_eth_unregister_device(eth_dev);
|
||||
ipa_eth_pci_free_device(eth_dev);
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_probe_handler(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
int rc = 0;
|
||||
struct ipa_eth_pci_driver *eth_pdrv;
|
||||
|
||||
ipa_eth_dbg("PCI probe called for %s driver with devfn %u",
|
||||
pdev->driver->name, pdev->devfn);
|
||||
|
||||
if (!ipa_eth_is_ready()) {
|
||||
ipa_eth_log(
|
||||
"Offload sub-system not initialized, deferring probe");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
eth_pdrv = lookup_eth_pdrv(pdev->driver);
|
||||
if (!eth_pdrv) {
|
||||
ipa_eth_bug("Failed to lookup epci driver");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
rc = eth_pdrv->probe_real(pdev, id);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed real PCI probe of devfn=%u");
|
||||
goto err_probe;
|
||||
}
|
||||
|
||||
rc = ipa_eth_pci_register_device(pdev, eth_pdrv);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to register PCI eth device");
|
||||
goto err_register;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_register:
|
||||
eth_pdrv->remove_real(pdev);
|
||||
err_probe:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ipa_eth_pci_remove_handler(struct pci_dev *pdev)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev = NULL;
|
||||
struct ipa_eth_pci_driver *eth_pdrv = NULL;
|
||||
|
||||
ipa_eth_dbg("PCI remove called for %s driver with devfn %u",
|
||||
pdev->driver->name, pdev->devfn);
|
||||
|
||||
eth_dev = lookup_eth_dev(pdev);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_bug("Failed to lookup pci_dev -> eth_dev");
|
||||
return;
|
||||
}
|
||||
|
||||
eth_pdrv = eth_dev_to_pdev(eth_dev)->eth_pdrv;
|
||||
|
||||
ipa_eth_pci_unregister_device(eth_dev);
|
||||
|
||||
eth_pdrv->remove_real(pdev);
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_suspend_handler(struct device *dev)
|
||||
{
|
||||
int rc = 0;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
eth_dev = dev_to_eth_dev(dev);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_bug("Failed to lookup pci_dev -> eth_dev");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (work_pending(ð_dev->refresh)) {
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Refresh work is pending, aborting suspend");
|
||||
|
||||
/* Just abort suspend. Since the wq is freezable, the work item
|
||||
* would get flushed before we get called again.
|
||||
*/
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/* When offload is started, PCI power collapse is already disabled by
|
||||
* the ipa_eth_pci_disable_pc() api. Nonetheless, we still need to do
|
||||
* a dummy PCI config space save so that the PCIe framework will not by
|
||||
* itself perform a config space save-restore.
|
||||
*/
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_STARTED) {
|
||||
ipa_eth_dev_dbg(eth_dev,
|
||||
"Device suspend performing dummy config space save");
|
||||
rc = pci_save_state(to_pci_dev(dev));
|
||||
} else {
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Device suspend delegated to net driver");
|
||||
rc = eth_dev_pm_ops(eth_dev)->suspend(dev);
|
||||
}
|
||||
|
||||
if (rc)
|
||||
ipa_eth_dev_err(eth_dev, "Device suspend failed");
|
||||
else
|
||||
ipa_eth_dev_dbg(eth_dev, "Device suspend complete");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_suspend_late_handler(struct device *dev)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
eth_dev = dev_to_eth_dev(dev);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_bug("Failed to lookup pci_dev -> eth_dev");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* In rare case where we detect some interface activity between the
|
||||
* time PM_SUSPEND_PREPARE event was processed and the device was
|
||||
* actually frozen, abort the suspend operation.
|
||||
*/
|
||||
if (ipa_eth_net_check_active(eth_dev)) {
|
||||
pr_info("%s: %s shows late stage activity, preventing suspend\n",
|
||||
IPA_ETH_SUBSYS, eth_dev->net_dev->name);
|
||||
|
||||
/* Have PM_SUSPEND_PREPARE give us one wakeup time quanta */
|
||||
ipa_eth_dev_assume_active_inc(eth_dev, 1);
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_resume_early_handler(struct device *dev)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
eth_dev = dev_to_eth_dev(dev);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_bug("Failed to lookup pci_dev -> eth_dev");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* We cannot check start_on_resume in the resume handler as it can get
|
||||
* invoked also if .suspend_late() aborts due to interface activity.
|
||||
*/
|
||||
if (eth_dev->start_on_resume && !eth_dev->start) {
|
||||
eth_dev->start = true;
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_resume_handler(struct device *dev)
|
||||
{
|
||||
int rc = 0;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
struct pci_dev *pci_dev = to_pci_dev(dev);
|
||||
|
||||
eth_dev = lookup_eth_dev(pci_dev);
|
||||
if (!eth_dev) {
|
||||
ipa_eth_bug("Failed to lookup pci_dev -> eth_dev");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* During suspend, RC power collapse would not have happened if offload
|
||||
* was started. Ignore resume callback since the device does not need
|
||||
* to be re-initialized.
|
||||
*/
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_STARTED) {
|
||||
ipa_eth_dev_dbg(eth_dev,
|
||||
"Device resume performing nop");
|
||||
rc = 0;
|
||||
} else {
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Device resume delegated to net driver");
|
||||
rc = eth_dev_pm_ops(eth_dev)->resume(dev);
|
||||
|
||||
/* Give some time for device to settle after a resume */
|
||||
ipa_eth_dev_assume_active_ms(eth_dev, IPA_ETH_RESUME_SETTLE_MS);
|
||||
}
|
||||
|
||||
if (rc)
|
||||
ipa_eth_dev_err(eth_dev, "Device resume failed");
|
||||
else
|
||||
ipa_eth_dev_dbg(eth_dev, "Device resume complete");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* MSM PCIe driver invokes only suspend and resume callbacks, other operations
|
||||
* can be ignored unless we see a client requiring the feature.
|
||||
*/
|
||||
static const struct dev_pm_ops ipa_eth_pci_pm_ops = {
|
||||
.suspend = ipa_eth_pci_suspend_handler,
|
||||
.suspend_late = ipa_eth_pci_suspend_late_handler,
|
||||
.resume_early = ipa_eth_pci_resume_early_handler,
|
||||
.resume = ipa_eth_pci_resume_handler,
|
||||
};
|
||||
|
||||
static bool ipa_eth_pci_check_driver(struct pci_driver *pdrv)
|
||||
{
|
||||
return
|
||||
pdrv->name &&
|
||||
pdrv->probe &&
|
||||
pdrv->remove &&
|
||||
pdrv->driver.pm &&
|
||||
pdrv->driver.pm->suspend &&
|
||||
pdrv->driver.pm->resume;
|
||||
}
|
||||
|
||||
static struct ipa_eth_pci_driver *ipa_eth_pci_setup_driver(
|
||||
struct pci_driver *pdrv,
|
||||
struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
struct ipa_eth_pci_driver *eth_pdrv;
|
||||
|
||||
eth_pdrv = kzalloc(sizeof(*eth_pdrv), GFP_KERNEL);
|
||||
if (!eth_pdrv)
|
||||
return NULL;
|
||||
|
||||
eth_pdrv->probe_real = pdrv->probe;
|
||||
pdrv->probe = ipa_eth_pci_probe_handler;
|
||||
|
||||
eth_pdrv->remove_real = pdrv->remove;
|
||||
pdrv->remove = ipa_eth_pci_remove_handler;
|
||||
|
||||
eth_pdrv->pm_ops_real = pdrv->driver.pm;
|
||||
pdrv->driver.pm = &ipa_eth_pci_pm_ops;
|
||||
|
||||
eth_pdrv->nd = nd;
|
||||
nd->bus_priv = eth_pdrv;
|
||||
|
||||
return eth_pdrv;
|
||||
}
|
||||
|
||||
static void ipa_eth_pci_reset_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
struct pci_driver *pdrv = to_pci_driver(nd->driver);
|
||||
struct ipa_eth_pci_driver *eth_pdrv = nd->bus_priv;
|
||||
|
||||
if (is_driver_used(eth_pdrv))
|
||||
ipa_eth_bug("Driver is still being used by a device");
|
||||
|
||||
nd->bus_priv = NULL;
|
||||
|
||||
pdrv->probe = eth_pdrv->probe_real;
|
||||
pdrv->remove = eth_pdrv->remove_real;
|
||||
pdrv->driver.pm = eth_pdrv->pm_ops_real;
|
||||
|
||||
memset(eth_pdrv, 0, sizeof(*eth_pdrv));
|
||||
kfree(eth_pdrv);
|
||||
}
|
||||
|
||||
static int ipa_eth_pci_register_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
struct pci_driver *pdrv;
|
||||
struct ipa_eth_pci_driver *eth_pdrv;
|
||||
|
||||
pdrv = to_pci_driver(nd->driver);
|
||||
|
||||
if (!ipa_eth_pci_check_driver(pdrv)) {
|
||||
ipa_eth_err("PCI driver validation failed for %s", nd->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
eth_pdrv = ipa_eth_pci_setup_driver(pdrv, nd);
|
||||
if (!eth_pdrv)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&ipa_eth_pci_drivers_mutex);
|
||||
list_add(&nd->bus_driver_list, &ipa_eth_pci_drivers);
|
||||
mutex_unlock(&ipa_eth_pci_drivers_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ipa_eth_pci_unregister_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
|
||||
mutex_lock(&ipa_eth_pci_drivers_mutex);
|
||||
list_del(&nd->bus_driver_list);
|
||||
mutex_unlock(&ipa_eth_pci_drivers_mutex);
|
||||
|
||||
ipa_eth_pci_reset_driver(nd);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_pci_enable_pc() - Permit power collapse of the PCI root port
|
||||
* @eth_dev: Device attached to the PCI bus
|
||||
*
|
||||
* This function instructs the MSM PCIe bus driver to permit power collapse
|
||||
* of the root complex when Linux goes to suspend state.
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
static int ipa_eth_pci_enable_pc(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct pci_dev *pci_dev = to_pci_dev(eth_dev->dev);
|
||||
|
||||
rc = msm_pcie_pm_control(MSM_PCIE_ENABLE_PC,
|
||||
pci_dev->bus->number, pci_dev, NULL, MSM_PCIE_CONFIG_INVALID);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to enable MSM PCIe power collapse");
|
||||
} else {
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Enabled MSM PCIe power collapse");
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_pci_disable_pc() - Prevent power collapse of the PCI root port
|
||||
* @eth_dev: Device attached to the PCI bus
|
||||
*
|
||||
* This function instructs the MSM PCIe bus driver to prevent power collapse
|
||||
* of the root complex and connected EPs when Linux goes to suspend state.
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
static int ipa_eth_pci_disable_pc(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct pci_dev *pci_dev = to_pci_dev(eth_dev->dev);
|
||||
|
||||
rc = msm_pcie_pm_control(MSM_PCIE_DISABLE_PC,
|
||||
pci_dev->bus->number, pci_dev, NULL, MSM_PCIE_CONFIG_INVALID);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to disable MSM PCIe power collapse");
|
||||
} else {
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Disabled MSM PCIe power collapse");
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct ipa_eth_bus ipa_eth_pci_bus = {
|
||||
.bus = &pci_bus_type,
|
||||
.register_driver = ipa_eth_pci_register_driver,
|
||||
.unregister_driver = ipa_eth_pci_unregister_driver,
|
||||
.enable_pc = ipa_eth_pci_enable_pc,
|
||||
.disable_pc = ipa_eth_pci_disable_pc,
|
||||
};
|
||||
|
||||
int ipa_eth_pci_modinit(void)
|
||||
{
|
||||
ipa_eth_pci_is_ready = true;
|
||||
|
||||
ipa_eth_log("Offload sub-system pci bus module init is complete");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ipa_eth_pci_modexit(void)
|
||||
{
|
||||
ipa_eth_log("De-initing offload sub-system pci bus module");
|
||||
|
||||
if (!ipa_eth_pci_is_ready)
|
||||
return;
|
||||
|
||||
ipa_eth_pci_is_ready = false;
|
||||
}
|
||||
131
drivers/platform/msm/ethernet/ipa_eth_pm.c
Normal file
131
drivers/platform/msm/ethernet/ipa_eth_pm.c
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
#define IPA_ETH_MIN_BW_MBPS 1
|
||||
#define IPA_ETH_MAX_BW_MBPS 100000
|
||||
|
||||
static void ipa_eth_pm_callback(void *arg, enum ipa_pm_cb_event event)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev = (struct ipa_eth_device *)arg;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Received PM event 0x%x", event);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_pm_register() - Register offload device with IPA PM
|
||||
* @eth_dev: Offload device to register with PM
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_pm_register(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc = 0;
|
||||
char name[IPA_PM_MAX_EX_CL];
|
||||
struct ipa_pm_register_params pm_params;
|
||||
|
||||
if (!ipa_pm_is_used())
|
||||
return 0;
|
||||
|
||||
if (eth_dev->pm_handle != IPA_PM_MAX_CLIENTS)
|
||||
return -EEXIST;
|
||||
|
||||
snprintf(name, sizeof(name), IPA_ETH_SUBSYS ":%s",
|
||||
eth_dev->net_dev->name);
|
||||
|
||||
memset(&pm_params, 0, sizeof(pm_params));
|
||||
pm_params.name = name;
|
||||
pm_params.callback = ipa_eth_pm_callback;
|
||||
pm_params.user_data = eth_dev;
|
||||
pm_params.group = IPA_PM_GROUP_DEFAULT;
|
||||
pm_params.skip_clk_vote = false;
|
||||
|
||||
rc = ipa_pm_register(&pm_params, ð_dev->pm_handle);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to register with IPA PM");
|
||||
|
||||
eth_dev->pm_handle = IPA_PM_MAX_CLIENTS;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_pm_unregister() - Unregister offload device from IPA PM
|
||||
* @eth_dev: Offload device to unregister
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_pm_unregister(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!ipa_pm_is_used())
|
||||
return 0;
|
||||
|
||||
rc = ipa_pm_deregister(eth_dev->pm_handle);
|
||||
if (rc)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to deregister from IPA PM");
|
||||
else
|
||||
eth_dev->pm_handle = IPA_PM_MAX_CLIENTS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int ipa_eth_pm_activate(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
if (!ipa_pm_is_used())
|
||||
return 0;
|
||||
|
||||
return ipa_pm_activate_sync(eth_dev->pm_handle);
|
||||
}
|
||||
|
||||
int ipa_eth_pm_deactivate(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
if (!ipa_pm_is_used())
|
||||
return 0;
|
||||
|
||||
return ipa_pm_deactivate_sync(eth_dev->pm_handle);
|
||||
}
|
||||
|
||||
static u32 __fetch_ethtool_link_speed(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct ethtool_link_ksettings link_ksettings;
|
||||
|
||||
rtnl_lock();
|
||||
rc = __ethtool_get_link_ksettings(eth_dev->net_dev, &link_ksettings);
|
||||
rtnl_unlock();
|
||||
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to obtain link settings via ethtool");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return link_ksettings.base.speed;
|
||||
}
|
||||
|
||||
int ipa_eth_pm_vote_bw(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
u32 link_speed;
|
||||
int throughput;
|
||||
|
||||
if (!ipa_pm_is_used())
|
||||
return 0;
|
||||
|
||||
link_speed = __fetch_ethtool_link_speed(eth_dev);
|
||||
throughput = clamp_val(link_speed,
|
||||
IPA_ETH_MIN_BW_MBPS, IPA_ETH_MAX_BW_MBPS);
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Link speed is %u, setting throughput as %d",
|
||||
link_speed, throughput);
|
||||
|
||||
return ipa_pm_set_throughput(eth_dev->pm_handle, throughput);
|
||||
}
|
||||
7
drivers/platform/msm/ethernet/ipa_eth_trace.c
Normal file
7
drivers/platform/msm/ethernet/ipa_eth_trace.c
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "ipa_eth_trace.h"
|
||||
85
drivers/platform/msm/ethernet/ipa_eth_trace.h
Normal file
85
drivers/platform/msm/ethernet/ipa_eth_trace.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM ipa_eth
|
||||
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#define TRACE_INCLUDE_FILE ipa_eth_trace
|
||||
|
||||
#if !defined(_IPA_ETH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _IPA_ETH_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
/*
|
||||
* Tracepoint for tracking all packets received through IPA LAN exception.
|
||||
*/
|
||||
TRACE_EVENT(lan_rx_skb,
|
||||
TP_PROTO(struct ipa_eth_channel *ch, struct sk_buff *skb),
|
||||
TP_ARGS(ch, skb),
|
||||
TP_STRUCT__entry(
|
||||
__field(const char *, dev)
|
||||
__field(const struct ethhdr *, eth_hdr)
|
||||
__field(unsigned int, eth_proto)
|
||||
__field(unsigned int, skb_len)
|
||||
__field(u64, count_total)
|
||||
__field(u64, count_drops)
|
||||
__field(u64, count_loopback)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->dev = ch->eth_dev->net_dev->name;
|
||||
__entry->eth_hdr = (struct ethhdr *)skb->data;
|
||||
__entry->eth_proto = ntohs(__entry->eth_hdr->h_proto);
|
||||
__entry->skb_len = skb->len;
|
||||
__entry->count_total = ch->exception_total;
|
||||
__entry->count_drops = ch->exception_drops;
|
||||
__entry->count_loopback = ch->exception_loopback;
|
||||
),
|
||||
TP_printk(
|
||||
"dev=%s count_total=%llu count_drops=%llu count_loopback=%llu eth_proto=%04X skb_len=%u",
|
||||
__entry->dev, __entry->count_total, __entry->count_drops,
|
||||
__entry->count_loopback, __entry->eth_proto, __entry->skb_len
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Tracepoint for ethernet link activity check by PM.
|
||||
*/
|
||||
TRACE_EVENT(net_check_active,
|
||||
TP_PROTO(
|
||||
struct ipa_eth_device *eth_dev,
|
||||
struct rtnl_link_stats64 *old_stats,
|
||||
struct rtnl_link_stats64 *new_stats,
|
||||
unsigned long assume_active
|
||||
),
|
||||
TP_ARGS(eth_dev, old_stats, new_stats, assume_active),
|
||||
TP_STRUCT__entry(
|
||||
__field(const char *, dev)
|
||||
__field(u64, old_rx_packets)
|
||||
__field(u64, new_rx_packets)
|
||||
__field(u64, diff_rx_packets)
|
||||
__field(unsigned long, assume_active)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->dev = eth_dev->net_dev->name;
|
||||
__entry->old_rx_packets = old_stats->rx_packets;
|
||||
__entry->new_rx_packets = new_stats->rx_packets;
|
||||
__entry->diff_rx_packets =
|
||||
__entry->new_rx_packets - __entry->old_rx_packets;
|
||||
__entry->assume_active = assume_active;
|
||||
),
|
||||
TP_printk("dev=%s assume_active=%lu rx_total=%llu rx_diff=+%llu",
|
||||
__entry->dev, __entry->assume_active,
|
||||
__entry->new_rx_packets, __entry->diff_rx_packets
|
||||
)
|
||||
);
|
||||
|
||||
#endif /* _IPA_ETH_TRACE_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#include <trace/define_trace.h>
|
||||
224
drivers/platform/msm/ethernet/ipa_eth_uc.c
Normal file
224
drivers/platform/msm/ethernet/ipa_eth_uc.c
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include "../ipa_i.h"
|
||||
#include "../ipa_uc_offload_i.h"
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
#define IPA_ETH_UC_RESPONSE_SUCCESS \
|
||||
FEATURE_ENUM_VAL(IPA_HW_FEATURE_OFFLOAD, IPA_ETH_UC_RSP_SUCCESS)
|
||||
|
||||
struct ipa_eth_uc_cmd_param {
|
||||
u32 protocol;
|
||||
u8 protocol_data[];
|
||||
} __packed;
|
||||
|
||||
struct ipa_eth_uc_cmd_param_mem {
|
||||
struct ipa_eth_uc_cmd_param *param;
|
||||
dma_addr_t dma_addr;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct ipa_eth_uc_cmd_ctx {
|
||||
u8 cmd_op;
|
||||
u32 protocol;
|
||||
const void *protocol_data;
|
||||
size_t data_size;
|
||||
|
||||
struct ipa_eth_uc_cmd_param_mem pmem;
|
||||
};
|
||||
|
||||
static int ipa_eth_uc_init_cmd_ctx(struct ipa_eth_uc_cmd_ctx *cmd_ctx,
|
||||
enum ipa_eth_uc_op op, u32 protocol,
|
||||
const void *prot_data, size_t datasz)
|
||||
{
|
||||
struct ipa_eth_uc_cmd_param_mem *pmem = &cmd_ctx->pmem;
|
||||
|
||||
if (op == IPA_ETH_UC_OP_NOP || op >= IPA_ETH_UC_OP_MAX) {
|
||||
ipa_eth_err("Invalid uC op code");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cmd_ctx->cmd_op = FEATURE_ENUM_VAL(IPA_HW_FEATURE_OFFLOAD, op);
|
||||
cmd_ctx->protocol = protocol;
|
||||
cmd_ctx->protocol_data = prot_data;
|
||||
cmd_ctx->data_size = datasz;
|
||||
|
||||
pmem->size = sizeof(*pmem->param) + datasz;
|
||||
pmem->param = dma_alloc_coherent(ipa3_ctx->uc_pdev, pmem->size,
|
||||
&pmem->dma_addr, GFP_KERNEL);
|
||||
if (!pmem->param)
|
||||
return -ENOMEM;
|
||||
|
||||
pmem->param->protocol = protocol;
|
||||
memcpy(pmem->param->protocol_data, prot_data, datasz);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ipa_eth_uc_deinit_cmd_ctx(struct ipa_eth_uc_cmd_ctx *cmd_ctx)
|
||||
{
|
||||
struct ipa_eth_uc_cmd_param_mem *pmem = &cmd_ctx->pmem;
|
||||
|
||||
dma_free_coherent(ipa3_ctx->uc_pdev, pmem->size,
|
||||
pmem->param, pmem->dma_addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_uc_send_cmd() - Send an offload command to IPA uC
|
||||
* @op: uC offload op code
|
||||
* @protocol: uC offload protocol value
|
||||
* @prot_data: uC offload command data, specific to the protocol
|
||||
* @datasz: size of command data
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_uc_send_cmd(enum ipa_eth_uc_op op, u32 protocol,
|
||||
const void *prot_data, size_t datasz)
|
||||
{
|
||||
int rc = 0;
|
||||
struct ipa_eth_uc_cmd_ctx cmd_ctx;
|
||||
|
||||
rc = ipa_eth_uc_init_cmd_ctx(&cmd_ctx, op, protocol, prot_data, datasz);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to init command context for op=%u", op);
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_log("Sending uC command, op=%u, prot=%u, data size=%u",
|
||||
cmd_ctx.cmd_op, cmd_ctx.protocol, cmd_ctx.data_size);
|
||||
|
||||
rc = ipa3_uc_send_cmd((u32)cmd_ctx.pmem.dma_addr, cmd_ctx.cmd_op,
|
||||
IPA_ETH_UC_RESPONSE_SUCCESS, false, 10*HZ);
|
||||
|
||||
ipa_eth_uc_deinit_cmd_ctx(&cmd_ctx);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_uc_send_cmd);
|
||||
|
||||
/* Define IPA hardware constants not available from IPA header files */
|
||||
|
||||
#define IPA_HW_DIR_PRODUCER 0
|
||||
#define IPA_HW_DIR_CONSUMER 1
|
||||
|
||||
#define IPA_HW_CH_ID_INVALID 0xFF
|
||||
#define IPA_HW_PROTOCOL_INVALID 0xFFFFFFFF
|
||||
|
||||
static u32 find_uc_protocol(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int protocol;
|
||||
struct ipa_eth_channel *ch;
|
||||
|
||||
list_for_each_entry(ch, ð_dev->rx_channels, channel_list) {
|
||||
protocol = ipa_get_prot_id(ch->ipa_client);
|
||||
if (protocol >= 0)
|
||||
return lower_32_bits(protocol);
|
||||
}
|
||||
|
||||
list_for_each_entry(ch, ð_dev->tx_channels, channel_list) {
|
||||
protocol = ipa_get_prot_id(ch->ipa_client);
|
||||
if (protocol >= 0)
|
||||
return lower_32_bits(protocol);
|
||||
}
|
||||
|
||||
return IPA_HW_PROTOCOL_INVALID;
|
||||
}
|
||||
|
||||
static int find_client_channel(enum ipa_client_type client)
|
||||
{
|
||||
const struct ipa_gsi_ep_config *gsi_ep_cfg;
|
||||
|
||||
gsi_ep_cfg = ipa3_get_gsi_ep_info(client);
|
||||
if (!gsi_ep_cfg)
|
||||
return -EFAULT;
|
||||
|
||||
return gsi_ep_cfg->ipa_gsi_chan_num;
|
||||
}
|
||||
|
||||
int ipa_eth_uc_stats_init(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_uc_stats_deinit(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
u32 protocol = find_uc_protocol(eth_dev);
|
||||
|
||||
if (protocol == IPA_HW_PROTOCOL_INVALID)
|
||||
return -EFAULT;
|
||||
|
||||
return ipa_uc_debug_stats_dealloc(protocol);
|
||||
}
|
||||
|
||||
static void __fill_stats_info(
|
||||
struct ipa_eth_channel *ch,
|
||||
struct IpaOffloadStatschannel_info *ch_info,
|
||||
bool start)
|
||||
{
|
||||
ch_info->dir = IPA_ETH_CH_IS_RX(ch) ?
|
||||
IPA_HW_DIR_CONSUMER : IPA_HW_DIR_PRODUCER;
|
||||
|
||||
if (start) {
|
||||
int gsi_ch = find_client_channel(ch->ipa_client);
|
||||
|
||||
if (gsi_ch < 0) {
|
||||
ipa_eth_dev_err(ch->eth_dev,
|
||||
"Failed to determine GSI channel for client %d",
|
||||
ch->ipa_client);
|
||||
gsi_ch = IPA_HW_CH_ID_INVALID;
|
||||
}
|
||||
|
||||
ch_info->ch_id = (u8) gsi_ch;
|
||||
} else {
|
||||
ch_info->ch_id = IPA_HW_CH_ID_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static int ipa_eth_uc_stats_control(struct ipa_eth_device *eth_dev, bool start)
|
||||
{
|
||||
int stats_idx = 0;
|
||||
struct ipa_eth_channel *ch;
|
||||
u32 protocol = find_uc_protocol(eth_dev);
|
||||
struct IpaHwOffloadStatsAllocCmdData_t stats_info;
|
||||
|
||||
if (protocol == IPA_HW_PROTOCOL_INVALID) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed find to uC protocol");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
memset(&stats_info, 0, sizeof(stats_info));
|
||||
|
||||
stats_info.protocol = protocol;
|
||||
|
||||
list_for_each_entry(ch, ð_dev->rx_channels, channel_list) {
|
||||
if (stats_idx == IPA_MAX_CH_STATS_SUPPORTED)
|
||||
break;
|
||||
|
||||
__fill_stats_info(ch,
|
||||
&stats_info.ch_id_info[stats_idx++], start);
|
||||
}
|
||||
|
||||
list_for_each_entry(ch, ð_dev->tx_channels, channel_list) {
|
||||
if (stats_idx == IPA_MAX_CH_STATS_SUPPORTED)
|
||||
break;
|
||||
|
||||
__fill_stats_info(ch,
|
||||
&stats_info.ch_id_info[stats_idx++], start);
|
||||
}
|
||||
|
||||
return ipa_uc_debug_stats_alloc(stats_info);
|
||||
}
|
||||
|
||||
int ipa_eth_uc_stats_start(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return ipa_eth_uc_stats_control(eth_dev, true);
|
||||
}
|
||||
|
||||
int ipa_eth_uc_stats_stop(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return ipa_eth_uc_stats_control(eth_dev, false);
|
||||
}
|
||||
154
drivers/platform/msm/ethernet/ipa_eth_utils.c
Normal file
154
drivers/platform/msm/ethernet/ipa_eth_utils.c
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
#ifdef CONFIG_IPA_ETH_DEBUG
|
||||
bool ipa_eth_ipc_logdbg = true;
|
||||
#else
|
||||
bool ipa_eth_ipc_logdbg;
|
||||
#endif
|
||||
|
||||
static const char * const ipa_eth_device_events[IPA_ETH_DEV_EVENT_COUNT] = {
|
||||
[IPA_ETH_DEV_RESET_PREPARE] = "RESET_PREPARE",
|
||||
[IPA_ETH_DEV_RESET_COMPLETE] = "RESET_COMPLETE",
|
||||
[IPA_ETH_DEV_ADD_MACSEC_IF] = "ADD_MACSEC_IF",
|
||||
[IPA_ETH_DEV_DEL_MACSEC_IF] = "DEL_MACSEC_IF",
|
||||
};
|
||||
|
||||
const char *ipa_eth_device_event_name(enum ipa_eth_device_event event)
|
||||
{
|
||||
const char *name = "<unknown>";
|
||||
|
||||
if (event < IPA_ETH_DEV_EVENT_COUNT && ipa_eth_device_events[event])
|
||||
name = ipa_eth_device_events[event];
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static const char * const
|
||||
ipa_eth_net_device_events[IPA_ETH_NET_DEVICE_MAX_EVENTS] = {
|
||||
[NETDEV_REGISTER] = "REGISTER",
|
||||
[NETDEV_UNREGISTER] = "UNREGISTER",
|
||||
[NETDEV_CHANGENAME] = "CHANGE_NAME",
|
||||
[NETDEV_PRE_UP] = "PRE_UP",
|
||||
[NETDEV_UP] = "UP",
|
||||
[NETDEV_GOING_DOWN] = "GOING_DOWN",
|
||||
[NETDEV_DOWN] = "DOWN",
|
||||
[NETDEV_CHANGE] = "CHANGE",
|
||||
[NETDEV_CHANGELOWERSTATE] = "CHANGE_LOWER_STATE",
|
||||
[NETDEV_PRECHANGEUPPER] = "PRE_CHANGE_UPPER",
|
||||
[NETDEV_CHANGEUPPER] = "CHANGE_UPPER",
|
||||
[NETDEV_JOIN] = "JOIN",
|
||||
};
|
||||
|
||||
const char *ipa_eth_net_device_event_name(unsigned long event)
|
||||
{
|
||||
const char *name = "<unknown>";
|
||||
|
||||
if (event < IPA_ETH_NET_DEVICE_MAX_EVENTS &&
|
||||
ipa_eth_net_device_events[event])
|
||||
name = ipa_eth_net_device_events[event];
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static const char * const
|
||||
ipa_eth_pm_notifier_events[IPA_ETH_PM_NOTIFIER_MAX_EVENTS] = {
|
||||
[PM_HIBERNATION_PREPARE] = "HIBERNATION_PREPARE",
|
||||
[PM_POST_HIBERNATION] = "POST_HIBERNATION",
|
||||
[PM_SUSPEND_PREPARE] = "SUSPEND_PREPARE",
|
||||
[PM_POST_SUSPEND] = "POST_SUSPEND",
|
||||
[PM_RESTORE_PREPARE] = "RESTORE_PREPARE",
|
||||
[PM_POST_RESTORE] = "POST_RESTORE",
|
||||
};
|
||||
|
||||
const char *ipa_eth_pm_notifier_event_name(unsigned long event)
|
||||
{
|
||||
const char *name = "<unknown>";
|
||||
|
||||
if (event < IPA_ETH_PM_NOTIFIER_MAX_EVENTS &&
|
||||
ipa_eth_pm_notifier_events[event])
|
||||
name = ipa_eth_pm_notifier_events[event];
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static void __ipa_eth_free_msg(void *buff, u32 len, u32 type) {}
|
||||
|
||||
static int ipa_eth_send_ecm_msg(struct net_device *net_dev,
|
||||
enum ipa_ecm_event ecm_event)
|
||||
{
|
||||
struct ipa_msg_meta msg_meta;
|
||||
struct ipa_ecm_msg ecm_msg;
|
||||
|
||||
if (!net_dev)
|
||||
return -EFAULT;
|
||||
|
||||
memset(&msg_meta, 0, sizeof(msg_meta));
|
||||
memset(&ecm_msg, 0, sizeof(ecm_msg));
|
||||
|
||||
ecm_msg.ifindex = net_dev->ifindex;
|
||||
strlcpy(ecm_msg.name, net_dev->name, IPA_RESOURCE_NAME_MAX);
|
||||
|
||||
msg_meta.msg_type = ecm_event;
|
||||
msg_meta.msg_len = sizeof(struct ipa_ecm_msg);
|
||||
|
||||
return ipa_send_msg(&msg_meta, &ecm_msg, __ipa_eth_free_msg);
|
||||
}
|
||||
|
||||
int ipa_eth_send_msg_connect(struct net_device *net_dev)
|
||||
{
|
||||
ipa_eth_log("Sending ECM_CONNECT for %s", net_dev->name);
|
||||
return ipa_eth_send_ecm_msg(net_dev, ECM_CONNECT);
|
||||
}
|
||||
|
||||
int ipa_eth_send_msg_disconnect(struct net_device *net_dev)
|
||||
{
|
||||
ipa_eth_log("Sending ECM_DISCONNECT for %s", net_dev->name);
|
||||
return ipa_eth_send_ecm_msg(net_dev, ECM_DISCONNECT);
|
||||
}
|
||||
|
||||
/* IPC Logging */
|
||||
module_param(ipa_eth_ipc_logdbg, bool, 0444);
|
||||
MODULE_PARM_DESC(ipa_eth_ipc_logdbg, "Log debug IPC messages");
|
||||
|
||||
static void *ipa_eth_ipc_logbuf;
|
||||
|
||||
void *ipa_eth_get_ipc_logbuf(void)
|
||||
{
|
||||
return ipa_eth_ipc_logbuf;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_get_ipc_logbuf);
|
||||
|
||||
void *ipa_eth_get_ipc_logbuf_dbg(void)
|
||||
{
|
||||
return ipa_eth_ipc_logdbg ? ipa_eth_ipc_logbuf : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_get_ipc_logbuf_dbg);
|
||||
|
||||
#define IPA_ETH_IPC_LOG_PAGES 128
|
||||
|
||||
int ipa_eth_ipc_log_init(void)
|
||||
{
|
||||
if (ipa_eth_ipc_logbuf)
|
||||
return 0;
|
||||
|
||||
ipa_eth_ipc_logbuf = ipc_log_context_create(
|
||||
IPA_ETH_IPC_LOG_PAGES, IPA_ETH_SUBSYS, 0);
|
||||
|
||||
return ipa_eth_ipc_logbuf ? 0 : -EFAULT;
|
||||
}
|
||||
|
||||
void ipa_eth_ipc_log_cleanup(void)
|
||||
{
|
||||
if (ipa_eth_ipc_logbuf) {
|
||||
ipc_log_context_destroy(ipa_eth_ipc_logbuf);
|
||||
ipa_eth_ipc_logbuf = NULL;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue