Merge "msm: ipa: IPA offload subsystem for ethernet devices"

This commit is contained in:
qctecmdr 2020-12-22 07:07:16 -08:00 • committed by Gerrit - the friendly Code Review server
commit c0a8ba67dd
19 changed files with 7093 additions and 0 deletions

View file

@ -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

View file

@ -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/

View 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

File diff suppressed because it is too large Load diff

View 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();
}

View 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(&eth_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, &eth_dev->init,
&fops_eth_dev_init);
debugfs_create_file("start", 0644, eth_dev->debugfs, &eth_dev->start,
&fops_eth_dev_start);
debugfs_create_bool("start_on_wakeup", 0644,
eth_dev->debugfs, &eth_dev->start_on_wakeup);
debugfs_create_bool("start_on_resume", 0644,
eth_dev->debugfs, &eth_dev->start_on_resume);
debugfs_create_u32("start_on_timeout", 0644, eth_dev->debugfs,
&eth_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);
}

View 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_ */

View 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(&eth_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(&eth_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 = &eth_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(&eth_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, &eth_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 = &eth_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(&eth_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, &eth_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(&eth_dev->ipa_tx_intf, 0, sizeof(eth_dev->ipa_tx_intf));
kzfree(eth_dev->ipa_rx_intf.prop);
memset(&eth_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 = &eth_dev->ipa_tx_intf;
struct ipa_rx_intf *rx_intf = &eth_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 = &eth_dev->ipa_tx_intf;
struct ipa_rx_intf *rx_intf = &eth_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);

View 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);

View 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_

File diff suppressed because it is too large Load diff

View 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;
}

View 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(&eth_dev->bus_device_list, &ipa_eth_pci_devices);
mutex_unlock(&ipa_eth_pci_devices_mutex);
rc = msm_pcie_register_event(&eth_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(&eth_dev_to_pdev(eth_dev)->pcie_event);
mutex_lock(&ipa_eth_pci_devices_mutex);
list_del(&eth_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(&eth_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;
}

View 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, &eth_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);
}

View 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"

View 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>

View 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, &eth_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, &eth_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, &eth_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, &eth_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);
}

View 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