mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-07 20:33:58 -04:00
Merge "msm: ethernet: Remove IOSS v1 driver"
This commit is contained in:
commit
41a5acae7a
13 changed files with 0 additions and 3623 deletions
|
|
@ -195,32 +195,4 @@ config AQFWD_IOSS
|
|||
aqc_ioss.
|
||||
If unsure, say N.
|
||||
|
||||
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,5 +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/
|
||||
obj-$(CONFIG_VETH_IPA) += veth_ipa/
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
# 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_net.o \
|
||||
ipa_eth_offload.o \
|
||||
ipa_eth_pci.o \
|
||||
ipa_eth_utils.o
|
||||
|
|
@ -1,840 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/timer.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
unsigned long ipa_eth_state;
|
||||
|
||||
static LIST_HEAD(ipa_eth_devices);
|
||||
static DEFINE_MUTEX(ipa_eth_devices_lock);
|
||||
|
||||
#ifdef CONFIG_IPA_ETH_NOAUTO
|
||||
bool ipa_eth_noauto = true;
|
||||
#else
|
||||
bool ipa_eth_noauto;
|
||||
#endif
|
||||
|
||||
module_param(ipa_eth_noauto, bool, 0444);
|
||||
MODULE_PARM_DESC(ipa_eth_noauto,
|
||||
"Disable automatic offload initialization of interfaces");
|
||||
|
||||
static struct workqueue_struct *ipa_eth_wq;
|
||||
|
||||
bool ipa_eth_is_ready(void)
|
||||
{
|
||||
return test_bit(IPA_ETH_ST_READY, &ipa_eth_state);
|
||||
}
|
||||
|
||||
bool ipa_eth_all_ready(void)
|
||||
{
|
||||
return ipa_eth_is_ready() &&
|
||||
test_bit(IPA_ETH_ST_API_READY, &ipa_eth_state);
|
||||
}
|
||||
|
||||
static inline bool present(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return
|
||||
!test_bit(IPA_ETH_DEV_F_REMOVING, ð_dev->flags);
|
||||
}
|
||||
|
||||
static inline bool reachable(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return
|
||||
present(eth_dev) &&
|
||||
!test_bit(IPA_ETH_DEV_F_RESETTING, ð_dev->flags);
|
||||
}
|
||||
|
||||
static inline bool offloadable(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return
|
||||
ipa_eth_all_ready() &&
|
||||
reachable(eth_dev) &&
|
||||
!test_bit(IPA_ETH_DEV_F_UNPAIRING, ð_dev->flags);
|
||||
}
|
||||
|
||||
static inline bool initable(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return
|
||||
offloadable(eth_dev) &&
|
||||
test_bit(IPA_ETH_IF_ST_UP, ð_dev->if_state) &&
|
||||
eth_dev->init;
|
||||
}
|
||||
|
||||
static inline bool startable(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return
|
||||
initable(eth_dev) &&
|
||||
test_bit(IPA_ETH_IF_ST_LOWER_UP, ð_dev->if_state) &&
|
||||
eth_dev->start;
|
||||
}
|
||||
|
||||
static int ipa_eth_init_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_INITED)
|
||||
return 0;
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_DEINITED)
|
||||
return -EFAULT;
|
||||
|
||||
rc = ipa_eth_offload_init(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to init offload");
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Initialized device");
|
||||
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_INITED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_deinit_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_DEINITED)
|
||||
return 0;
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_INITED)
|
||||
return -EFAULT;
|
||||
|
||||
rc = ipa_eth_offload_deinit(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to deinit offload");
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Deinitialized device");
|
||||
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_DEINITED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_start_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_STARTED)
|
||||
return 0;
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_INITED)
|
||||
return -EFAULT;
|
||||
|
||||
rc = ipa_eth_offload_start(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to start offload");
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Started device");
|
||||
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_STARTED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_stop_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_DEINITED)
|
||||
return 0;
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_STARTED)
|
||||
return -EFAULT;
|
||||
|
||||
rc = ipa_eth_offload_stop(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to stop offload");
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Stopped device");
|
||||
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_INITED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ipa_eth_device_refresh(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
ipa_eth_dev_log(eth_dev, "Refreshing offload state for device");
|
||||
|
||||
if (!ipa_eth_offload_device_paired(eth_dev)) {
|
||||
ipa_eth_dev_log(eth_dev, "Device is not paired. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_ERROR) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Device in ERROR state, skipping refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
if (initable(eth_dev)) {
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_DEINITED) {
|
||||
(void) ipa_eth_init_device(eth_dev);
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_INITED) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to init device");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startable(eth_dev)) {
|
||||
(void) ipa_eth_start_device(eth_dev);
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_STARTED) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to start device");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ipa_eth_dev_log(eth_dev, "Start is disallowed for the device");
|
||||
|
||||
if (eth_dev->of_state == IPA_ETH_OF_ST_STARTED) {
|
||||
ipa_eth_stop_device(eth_dev);
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_INITED) {
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Failed to stop device");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initable(eth_dev)) {
|
||||
ipa_eth_dev_log(eth_dev, "Init is disallowed for the device");
|
||||
|
||||
ipa_eth_deinit_device(eth_dev);
|
||||
|
||||
if (eth_dev->of_state != IPA_ETH_OF_ST_DEINITED) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to deinit device");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ipa_eth_device_refresh_work(struct work_struct *work)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev = container_of(work,
|
||||
struct ipa_eth_device, refresh);
|
||||
|
||||
ipa_eth_device_refresh(eth_dev);
|
||||
}
|
||||
|
||||
void ipa_eth_device_refresh_sched(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
queue_work(ipa_eth_wq, ð_dev->refresh);
|
||||
}
|
||||
|
||||
void ipa_eth_device_refresh_sync(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
flush_work(ð_dev->refresh);
|
||||
}
|
||||
|
||||
static void ipa_eth_global_refresh_work(struct work_struct *work)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
ipa_eth_log("Performing global refresh");
|
||||
|
||||
mutex_lock(&ipa_eth_devices_lock);
|
||||
|
||||
if (ipa_eth_all_ready()) {
|
||||
list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) {
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&ipa_eth_devices_lock);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(ipa_eth_global_refresh, ipa_eth_global_refresh_work);
|
||||
|
||||
void ipa_eth_global_refresh_sched(void)
|
||||
{
|
||||
queue_work(ipa_eth_wq, &ipa_eth_global_refresh);
|
||||
}
|
||||
|
||||
static void ipa_eth_global_refresh_sync(void)
|
||||
{
|
||||
ipa_eth_global_refresh_sched();
|
||||
flush_workqueue(ipa_eth_wq);
|
||||
}
|
||||
|
||||
static int ipa_eth_device_prepare_reset(
|
||||
struct ipa_eth_device *eth_dev, void *data)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* Set the bit so that any in-progress operation can possibly
|
||||
* return early.
|
||||
*/
|
||||
set_bit(IPA_ETH_DEV_F_RESETTING, ð_dev->flags);
|
||||
|
||||
rc = ipa_eth_offload_prepare_reset(eth_dev, data);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ipa_eth_device_complete_reset(
|
||||
struct ipa_eth_device *eth_dev, void *data)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc = ipa_eth_offload_complete_reset(eth_dev, data);
|
||||
|
||||
/* Clear the flag before unlocking the mutex so that blocked threads
|
||||
* can resume with the updated value.
|
||||
*/
|
||||
clear_bit(IPA_ETH_DEV_F_RESETTING, ð_dev->flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ipa_eth_device_event_add_macsec(
|
||||
struct ipa_eth_device *eth_dev, void *data)
|
||||
{
|
||||
struct net_device *net_dev = data;
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Network device added MACSec interface %s", net_dev->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ipa_eth_device_event_del_macsec(
|
||||
struct ipa_eth_device *eth_dev, void *data)
|
||||
{
|
||||
struct net_device *net_dev = data;
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Network device removed MACSec interface %s", net_dev->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_device_notify() - Notifies a device event to the offload sub-system
|
||||
* @eth_dev: Device for which the event is generated
|
||||
* @event: Device event
|
||||
* @data: Event specific data, if required
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
int ipa_eth_device_notify(struct ipa_eth_device *eth_dev,
|
||||
enum ipa_eth_device_event event, void *data)
|
||||
{
|
||||
int rc = -EINVAL;
|
||||
|
||||
ipa_eth_dev_log(eth_dev,
|
||||
"Received notificaiton %s", ipa_eth_device_event_name(event));
|
||||
|
||||
switch (event) {
|
||||
case IPA_ETH_DEV_RESET_PREPARE:
|
||||
rc = ipa_eth_device_prepare_reset(eth_dev, data);
|
||||
break;
|
||||
case IPA_ETH_DEV_RESET_COMPLETE:
|
||||
rc = ipa_eth_device_complete_reset(eth_dev, data);
|
||||
break;
|
||||
case IPA_ETH_DEV_ADD_MACSEC_IF:
|
||||
rc = ipa_eth_device_event_add_macsec(eth_dev, data);
|
||||
break;
|
||||
case IPA_ETH_DEV_DEL_MACSEC_IF:
|
||||
rc = ipa_eth_device_event_del_macsec(eth_dev, data);
|
||||
break;
|
||||
default:
|
||||
ipa_eth_dev_log(eth_dev, "Unknown event %d", event);
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
ipa_eth_dev_err(eth_dev, "Failed to handle notification");
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_device_notify);
|
||||
|
||||
static void ipa_eth_dev_start_timer_cb(struct timer_list *t)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev = from_timer(eth_dev, t, start_timer);
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Start timer has fired");
|
||||
|
||||
/* Do not start offload if user disabled timer in between */
|
||||
if (present(eth_dev) && eth_dev->start_on_timeout) {
|
||||
eth_dev->start = true;
|
||||
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
}
|
||||
}
|
||||
|
||||
static int ipa_eth_panic_notifier(struct notifier_block *nb,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct ipa_eth_device_private *ipa_priv = container_of(nb,
|
||||
struct ipa_eth_device_private, panic_nb);
|
||||
struct ipa_eth_device *eth_dev = ipa_priv->eth_dev;
|
||||
|
||||
if (!reachable(eth_dev))
|
||||
return -ENODEV;
|
||||
|
||||
ipa_eth_net_save_regs(eth_dev);
|
||||
ipa_eth_offload_save_regs(eth_dev);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/* During a system suspend, suspend-prepare callbacks are called first by the
|
||||
* PM framework before freezing processes. This gives us an early opportunity
|
||||
* to abort the suspend and reduces the chances for device resumes at a later
|
||||
* stage.
|
||||
*/
|
||||
static int ipa_eth_pm_notifier_event_suspend_prepare(
|
||||
struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
/* We look for any ethernet rx activity since previous attempt to
|
||||
* suspend, and if such an activity is found, we hold a wake lock
|
||||
* for WAKE_TIME_MS time. Any Rx packets received beyond this point
|
||||
* should cause a wake up due to the Rx interrupt. In rare cases
|
||||
* where Rx interrupt was already processed and NAPI poll is yet to
|
||||
* complete, we perform a second check in the suspend late handler
|
||||
* and reverts the device suspension by aborting the system suspend.
|
||||
*/
|
||||
if (ipa_eth_net_check_active(eth_dev)) {
|
||||
pr_info("%s: %s is active, preventing suspend for %u ms\n",
|
||||
IPA_ETH_SUBSYS, eth_dev->net_dev->name,
|
||||
IPA_ETH_WAKE_TIME_MS);
|
||||
pm_wakeup_dev_event(eth_dev->dev, IPA_ETH_WAKE_TIME_MS, false);
|
||||
return NOTIFY_BAD;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int ipa_eth_pm_notifier_cb(struct notifier_block *nb,
|
||||
unsigned long pm_event, void *unused)
|
||||
{
|
||||
struct ipa_eth_device_private *ipa_priv = container_of(nb,
|
||||
struct ipa_eth_device_private, pm_nb);
|
||||
struct ipa_eth_device *eth_dev = ipa_priv->eth_dev;
|
||||
|
||||
ipa_eth_dbg("PM notifier called for event %s (0x%04lx)",
|
||||
ipa_eth_pm_notifier_event_name(pm_event), pm_event);
|
||||
|
||||
switch (pm_event) {
|
||||
case PM_SUSPEND_PREPARE:
|
||||
return ipa_eth_pm_notifier_event_suspend_prepare(eth_dev);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* ipa_eth_alloc_device() - Allocate an ipa_eth_device structure and initialize
|
||||
* all common fields
|
||||
* @dev: struct device pointer of the net device
|
||||
* @nd: Offload sub-system net driver structure
|
||||
*
|
||||
* This API is meant to be called by the bus layer (ex. ipa_eth_pci.c) to
|
||||
* allocate an ipa_eth_device object, initialize it with bus specific values
|
||||
* and register the discovered device using ipa_eth_register_device() API.
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
struct ipa_eth_device *ipa_eth_alloc_device(
|
||||
struct device *dev,
|
||||
struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
struct ipa_eth_device_private *ipa_priv;
|
||||
|
||||
if (!dev || !nd) {
|
||||
ipa_eth_err("Invalid device or net driver");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
eth_dev = kzalloc(sizeof(*eth_dev), GFP_KERNEL);
|
||||
if (!eth_dev)
|
||||
return NULL;
|
||||
|
||||
ipa_priv = kzalloc(sizeof(*ipa_priv), GFP_KERNEL);
|
||||
if (!ipa_priv) {
|
||||
kfree(eth_dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
eth_dev->dev = dev;
|
||||
eth_dev->nd = nd;
|
||||
|
||||
eth_dev->of_state = IPA_ETH_OF_ST_DEINITED;
|
||||
INIT_WORK(ð_dev->refresh, ipa_eth_device_refresh_work);
|
||||
|
||||
INIT_LIST_HEAD(ð_dev->rx_channels);
|
||||
INIT_LIST_HEAD(ð_dev->tx_channels);
|
||||
|
||||
timer_setup(ð_dev->start_timer, ipa_eth_dev_start_timer_cb, 0);
|
||||
|
||||
eth_dev->init = eth_dev->start = !ipa_eth_noauto;
|
||||
|
||||
/* Initialize private data */
|
||||
|
||||
ipa_priv->eth_dev = eth_dev;
|
||||
|
||||
INIT_LIST_HEAD(&ipa_priv->upper_devices);
|
||||
|
||||
ipa_priv->pm_nb.notifier_call = ipa_eth_pm_notifier_cb;
|
||||
ipa_priv->panic_nb.notifier_call = ipa_eth_panic_notifier;
|
||||
|
||||
eth_dev->ipa_priv = ipa_priv;
|
||||
|
||||
return eth_dev;
|
||||
}
|
||||
|
||||
/*
|
||||
* ipa_eth_free_device() - Free an ipa_eth_device object previously allocated
|
||||
* using ipa_eth_alloc_device()
|
||||
* @eth_dev: ipa_eth_device object to be freed
|
||||
*/
|
||||
void ipa_eth_free_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
kzfree(eth_dev->ipa_priv);
|
||||
kzfree(eth_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* ipa_eth_register_device() - Register a new device with offload sub-system
|
||||
* @eth_dev: Eth device to register
|
||||
*
|
||||
* This API is meant to be called by the bus layer (ex. ipa_eth_pci.c) to
|
||||
* register an ipa_eth_device object linked to a newly discovered device. The
|
||||
* @eth_dev object is expected to be allocated using ipa_eth_alloc_device() API.
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
int ipa_eth_register_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
struct ipa_eth_device_private *ipa_priv = eth_dev->ipa_priv;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Registering new device");
|
||||
|
||||
(void) atomic_notifier_chain_register(
|
||||
&panic_notifier_list, &ipa_priv->panic_nb);
|
||||
|
||||
rc = ipa_eth_net_open_device(eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to open network device");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Add new device to the devices list before pairing with offload
|
||||
* driver in order to synchronize with offload driver deregistration.
|
||||
*/
|
||||
mutex_lock(&ipa_eth_devices_lock);
|
||||
list_add(ð_dev->device_list, &ipa_eth_devices);
|
||||
mutex_unlock(&ipa_eth_devices_lock);
|
||||
|
||||
(void) register_pm_notifier(&ipa_priv->pm_nb);
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Registered new device");
|
||||
|
||||
rc = ipa_eth_offload_pair_device(eth_dev);
|
||||
if (rc)
|
||||
ipa_eth_dev_log(eth_dev, "Failed to pair device. Deferring.");
|
||||
|
||||
ipa_eth_debugfs_add_device(eth_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ipa_eth_unpair_device() - Unpair a device from its offload driver
|
||||
* @eth_dev: Device to unpair
|
||||
*
|
||||
* It is safe to call this function in parallel from ipa_eth_unregister_device()
|
||||
* and ipa_eth_unregister_offload_driver() as the necessary locking is placed
|
||||
* in ipa_eth_offload_unpair_device().
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
|
||||
static void ipa_eth_unpair_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
ipa_eth_dev_log(eth_dev, "Unpairing device");
|
||||
|
||||
/* Set UNPAIRING flag to prevent offload init or start */
|
||||
set_bit(IPA_ETH_DEV_F_UNPAIRING, ð_dev->flags);
|
||||
|
||||
/* Wait for next refresh to tear down offload path */
|
||||
ipa_eth_device_refresh_sync(eth_dev);
|
||||
|
||||
ipa_eth_offload_unpair_device(eth_dev);
|
||||
|
||||
clear_bit(IPA_ETH_DEV_F_UNPAIRING, ð_dev->flags);
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Unpaired device");
|
||||
}
|
||||
|
||||
/*
|
||||
* ipa_eth_unregister_device() - Unregister a device from the offload sub-system
|
||||
*
|
||||
* This API is meant to be called by the bus layer when a device is removed from
|
||||
* the system. The @eth_dev object itself need to be freed separately by calling
|
||||
* the ipa_eth_free_device() API.
|
||||
*/
|
||||
void ipa_eth_unregister_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
struct ipa_eth_device_private *ipa_priv = eth_dev->ipa_priv;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Unregistering device");
|
||||
|
||||
/* Set REMOVING flag so that device refreshes do not happen */
|
||||
set_bit(IPA_ETH_DEV_F_REMOVING, ð_dev->flags);
|
||||
|
||||
/* Remove debugfs node to prevent any new 'start_timer' to
|
||||
* be started.
|
||||
*/
|
||||
ipa_eth_debugfs_remove_device(eth_dev);
|
||||
|
||||
del_timer_sync(ð_dev->start_timer);
|
||||
|
||||
/* Unpair the device before removing from devices list so that
|
||||
* unregister_offload_driver() does not skip this device.
|
||||
*/
|
||||
ipa_eth_unpair_device(eth_dev);
|
||||
|
||||
(void) unregister_pm_notifier(&ipa_priv->pm_nb);
|
||||
|
||||
/* Remove from devices list so that no new global refreshes are
|
||||
* scheduled.
|
||||
*/
|
||||
mutex_lock(&ipa_eth_devices_lock);
|
||||
list_del(ð_dev->device_list);
|
||||
mutex_unlock(&ipa_eth_devices_lock);
|
||||
|
||||
/* Closing device from ipa_eth_net should prevent further events to be
|
||||
* received from either network driver via ipa_eth_device_notify() or
|
||||
* the network sub-system via netdevice notifier.
|
||||
*/
|
||||
ipa_eth_net_close_device(eth_dev);
|
||||
|
||||
/* No more refresh work would be queued beyond this point. Flush out
|
||||
* any pending refresh work items.
|
||||
*/
|
||||
cancel_work_sync(ð_dev->refresh);
|
||||
|
||||
/* By this time we would have processed all pending events for the
|
||||
* device and removed/disabled all event sources.
|
||||
*/
|
||||
clear_bit(IPA_ETH_DEV_F_REMOVING, ð_dev->flags);
|
||||
|
||||
(void) atomic_notifier_chain_unregister(
|
||||
&panic_notifier_list, &ipa_priv->panic_nb);
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Device unregistered");
|
||||
}
|
||||
|
||||
/**
|
||||
* ipa_eth_register_net_driver() - Register a network driver with the offload
|
||||
* subsystem
|
||||
* @nd: Network driver to register
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_register_net_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
return ipa_eth_net_register_driver(nd);
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_register_net_driver);
|
||||
|
||||
/**
|
||||
* ipa_eth_unregister_net_driver() - Unregister a network driver
|
||||
* @nd: Network driver to unregister
|
||||
*/
|
||||
void ipa_eth_unregister_net_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
ipa_eth_net_unregister_driver(nd);
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_unregister_net_driver);
|
||||
|
||||
|
||||
/**
|
||||
* ipa_eth_register_offload_driver - Register an offload driver with the offload
|
||||
* subsystem
|
||||
* @nd: Offload driver to register
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int ipa_eth_register_offload_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
int rc;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
rc = ipa_eth_offload_register_driver(od);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to register offload driver %s", od->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
ipa_eth_log("Registered offload driver %s", od->name);
|
||||
|
||||
mutex_lock(&ipa_eth_devices_lock);
|
||||
|
||||
list_for_each_entry(eth_dev, &ipa_eth_devices, device_list) {
|
||||
if (!ipa_eth_offload_pair_device(eth_dev))
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
}
|
||||
|
||||
mutex_unlock(&ipa_eth_devices_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_register_offload_driver);
|
||||
|
||||
/**
|
||||
* ipa_eth_unregister_offload_driver() - Unregister an offload driver
|
||||
* @nd: Offload driver to unregister
|
||||
*/
|
||||
void ipa_eth_unregister_offload_driver(struct ipa_eth_offload_driver *od)
|
||||
{
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
/* Unregister offload driver first from ipa_eth_offload so that any
|
||||
* new device registration will not be able to pair with the driver.
|
||||
*/
|
||||
ipa_eth_offload_unregister_driver(od);
|
||||
|
||||
mutex_lock(&ipa_eth_devices_lock);
|
||||
|
||||
list_for_each_entry(eth_dev, &ipa_eth_devices, device_list)
|
||||
if (eth_dev->od == od)
|
||||
ipa_eth_unpair_device(eth_dev);
|
||||
|
||||
mutex_unlock(&ipa_eth_devices_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(ipa_eth_unregister_offload_driver);
|
||||
|
||||
static void ipa_eth_api_ready_cb(void *user_data)
|
||||
{
|
||||
ipa_eth_log("IPA API is ready");
|
||||
|
||||
set_bit(IPA_ETH_ST_API_READY, &ipa_eth_state);
|
||||
ipa_eth_global_refresh_sched();
|
||||
}
|
||||
|
||||
static struct ipa_eth_ready eth_api_rdy = {
|
||||
.notify = ipa_eth_api_ready_cb,
|
||||
};
|
||||
|
||||
int ipa_eth_init(void)
|
||||
{
|
||||
int rc;
|
||||
unsigned int wq_flags = WQ_UNBOUND | WQ_MEM_RECLAIM;
|
||||
|
||||
/* Freeze the workqueue so that a refresh will not happen while the
|
||||
* device is suspended as the suspend operation itself can generate
|
||||
* Netlink events.
|
||||
*/
|
||||
wq_flags |= WQ_FREEZABLE;
|
||||
|
||||
rc = ipa_eth_ipc_log_init();
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to initialize IPC logging");
|
||||
goto err_ipclog;
|
||||
}
|
||||
|
||||
ipa_eth_dbg("Initializing IPA Ethernet Offload Sub-System");
|
||||
|
||||
ipa_eth_wq = alloc_workqueue("ipa_eth", wq_flags, 0);
|
||||
if (!ipa_eth_wq) {
|
||||
ipa_eth_err("Failed to alloc workqueue");
|
||||
goto err_wq;
|
||||
}
|
||||
|
||||
rc = ipa_eth_bus_modinit();
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to initialize bus");
|
||||
goto err_bus;
|
||||
}
|
||||
|
||||
rc = ipa_eth_debugfs_init();
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to initialize debugfs");
|
||||
goto err_dbgfs;
|
||||
}
|
||||
|
||||
rc = ipa_eth_register_ready_cb(ð_api_rdy);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to register ready cb with IPA driver");
|
||||
goto err_reg_rdy;
|
||||
}
|
||||
|
||||
set_bit(IPA_ETH_ST_READY, &ipa_eth_state);
|
||||
|
||||
ipa_eth_log("Offload sub-system init is complete");
|
||||
|
||||
ipa_eth_global_refresh_sched();
|
||||
|
||||
return 0;
|
||||
|
||||
err_reg_rdy:
|
||||
ipa_eth_debugfs_cleanup();
|
||||
err_dbgfs:
|
||||
ipa_eth_bus_modexit();
|
||||
err_bus:
|
||||
destroy_workqueue(ipa_eth_wq);
|
||||
ipa_eth_wq = NULL;
|
||||
err_wq:
|
||||
ipa_eth_ipc_log_cleanup();
|
||||
err_ipclog:
|
||||
return rc;
|
||||
}
|
||||
module_init(ipa_eth_init);
|
||||
|
||||
void ipa_eth_exit(void)
|
||||
{
|
||||
ipa_eth_dbg("De-initializing IPA Ethernet Offload Sub-System");
|
||||
|
||||
/* Clear READY bit so that all active offload paths can start
|
||||
* deinitializing.
|
||||
*/
|
||||
clear_bit(IPA_ETH_ST_READY, &ipa_eth_state);
|
||||
|
||||
ipa_eth_unregister_ready_cb(ð_api_rdy);
|
||||
|
||||
ipa_eth_debugfs_cleanup();
|
||||
|
||||
/* Wait for all offload paths to deinit. Although the chances for any
|
||||
* such path to exist is quite low when IPA is a platform device, we
|
||||
* still need to make sure we wait here before destroying the work
|
||||
* queue.
|
||||
*/
|
||||
ipa_eth_global_refresh_sync();
|
||||
|
||||
ipa_eth_bus_modexit();
|
||||
|
||||
destroy_workqueue(ipa_eth_wq);
|
||||
ipa_eth_wq = NULL;
|
||||
|
||||
ipa_eth_ipc_log_cleanup();
|
||||
}
|
||||
module_exit(ipa_eth_exit);
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
// 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();
|
||||
}
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
// 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)
|
||||
{
|
||||
ipa_eth_debugfs =
|
||||
debugfs_create_dir("ethernet", NULL);
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/* 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_ */
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
/* 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"
|
||||
|
||||
/* 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 NULL
|
||||
#define IPA_ETH_SUBSYS "IOSS"
|
||||
|
||||
#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;
|
||||
|
||||
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_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);
|
||||
|
||||
/* 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);
|
||||
|
||||
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_
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved
|
||||
*/
|
||||
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
#include "ipa_eth_i.h"
|
||||
|
||||
#define ipa_eth_nd_op(eth_dev, op, args...) (eth_dev->nd->ops->op(args))
|
||||
|
||||
static LIST_HEAD(ipa_eth_net_drivers);
|
||||
static DEFINE_MUTEX(ipa_eth_net_drivers_mutex);
|
||||
|
||||
static bool ipa_eth_net_check_ops(struct ipa_eth_net_ops *ops)
|
||||
{
|
||||
/* Following call backs are optional:
|
||||
* - save_regs()
|
||||
*/
|
||||
return
|
||||
ops &&
|
||||
ops->open_device &&
|
||||
ops->close_device;
|
||||
}
|
||||
|
||||
static bool ipa_eth_net_check_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
return
|
||||
nd &&
|
||||
nd->bus &&
|
||||
nd->name &&
|
||||
nd->driver &&
|
||||
ipa_eth_net_check_ops(nd->ops);
|
||||
}
|
||||
|
||||
static int __ipa_eth_net_register_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ipa_eth_bus_register_driver(nd);
|
||||
if (rc) {
|
||||
ipa_eth_err("Failed to register network driver %s", nd->name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
mutex_lock(&ipa_eth_net_drivers_mutex);
|
||||
list_add(&nd->driver_list, &ipa_eth_net_drivers);
|
||||
mutex_unlock(&ipa_eth_net_drivers_mutex);
|
||||
|
||||
ipa_eth_log("Registered network driver %s", nd->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipa_eth_net_register_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
if (!ipa_eth_net_check_driver(nd)) {
|
||||
ipa_eth_err("Net driver validation failed");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return __ipa_eth_net_register_driver(nd);
|
||||
}
|
||||
|
||||
void ipa_eth_net_unregister_driver(struct ipa_eth_net_driver *nd)
|
||||
{
|
||||
mutex_lock(&ipa_eth_net_drivers_mutex);
|
||||
list_del(&nd->driver_list);
|
||||
mutex_unlock(&ipa_eth_net_drivers_mutex);
|
||||
|
||||
ipa_eth_bus_unregister_driver(nd);
|
||||
}
|
||||
|
||||
static inline bool __is_netdev_link_up(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return netif_carrier_ok(eth_dev->net_dev);
|
||||
}
|
||||
|
||||
static inline bool __is_netdev_iface_up(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return !!(eth_dev->net_dev->flags & IFF_UP);
|
||||
}
|
||||
|
||||
bool ipa_eth_net_check_active(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
bool active = false;
|
||||
struct rtnl_link_stats64 curr_rtnl_stats;
|
||||
struct ipa_eth_device_private *ipa_priv = eth_dev_priv(eth_dev);
|
||||
struct rtnl_link_stats64 *last_rtnl_stats = &ipa_priv->last_rtnl_stats;
|
||||
|
||||
dev_get_stats(eth_dev->net_dev, &curr_rtnl_stats);
|
||||
|
||||
if (ipa_priv->assume_active) {
|
||||
ipa_priv->assume_active--;
|
||||
active = true;
|
||||
}
|
||||
|
||||
if (curr_rtnl_stats.rx_packets != last_rtnl_stats->rx_packets)
|
||||
active = true;
|
||||
|
||||
*last_rtnl_stats = curr_rtnl_stats;
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
static bool ipa_eth_net_update_link(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
return __is_netdev_link_up(eth_dev) ?
|
||||
!test_and_set_bit(IPA_ETH_IF_ST_LOWER_UP, ð_dev->if_state) :
|
||||
test_and_clear_bit(IPA_ETH_IF_ST_LOWER_UP, ð_dev->if_state);
|
||||
|
||||
}
|
||||
|
||||
static bool ipa_eth_net_event_up(struct ipa_eth_device *eth_dev,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
return !test_and_set_bit(IPA_ETH_IF_ST_UP, ð_dev->if_state);
|
||||
}
|
||||
|
||||
static bool ipa_eth_net_event_down(struct ipa_eth_device *eth_dev,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
return test_and_clear_bit(IPA_ETH_IF_ST_UP, ð_dev->if_state);
|
||||
}
|
||||
|
||||
static bool ipa_eth_net_event_change(struct ipa_eth_device *eth_dev,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
return ipa_eth_net_update_link(eth_dev);
|
||||
}
|
||||
|
||||
typedef bool (*ipa_eth_net_event_handler)(struct ipa_eth_device *eth_dev,
|
||||
unsigned long event, void *ptr);
|
||||
|
||||
/* Event handlers for netdevice events from real interface */
|
||||
static ipa_eth_net_event_handler
|
||||
ipa_eth_net_event_handlers[IPA_ETH_NET_DEVICE_MAX_EVENTS] = {
|
||||
[NETDEV_UP] = ipa_eth_net_event_up,
|
||||
[NETDEV_DOWN] = ipa_eth_net_event_down,
|
||||
[NETDEV_CHANGE] = ipa_eth_net_event_change,
|
||||
[NETDEV_CHANGELOWERSTATE] = ipa_eth_net_event_change,
|
||||
};
|
||||
|
||||
static int ipa_eth_net_device_event(struct notifier_block *nb,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct net_device *net_dev = netdev_notifier_info_to_dev(ptr);
|
||||
struct ipa_eth_device *eth_dev = container_of(nb,
|
||||
struct ipa_eth_device, netdevice_nb);
|
||||
|
||||
if (net_dev != eth_dev->net_dev)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
ipa_eth_dev_log(eth_dev, "Received netdev event %s (0x%04lx)",
|
||||
ipa_eth_net_device_event_name(event), event);
|
||||
|
||||
if (event < IPA_ETH_NET_DEVICE_MAX_EVENTS) {
|
||||
ipa_eth_net_event_handler handler =
|
||||
ipa_eth_net_event_handlers[event];
|
||||
bool refresh_needed = handler && handler(eth_dev, event, ptr);
|
||||
|
||||
/* We can not wait for refresh to complete as we are holding
|
||||
* the rtnl mutex.
|
||||
*/
|
||||
if (refresh_needed)
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
} else {
|
||||
ipa_eth_dev_err(eth_dev, "Event number out of bounds");
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
int ipa_eth_net_open_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ipa_eth_nd_op(eth_dev, open_device, eth_dev);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to open net device");
|
||||
goto err_open;
|
||||
}
|
||||
|
||||
if (!eth_dev->net_dev) {
|
||||
rc = -EFAULT;
|
||||
ipa_eth_dev_err(eth_dev,
|
||||
"Network driver failed to fill net_dev");
|
||||
goto err_net_dev;
|
||||
}
|
||||
|
||||
eth_dev->netdevice_nb.notifier_call = ipa_eth_net_device_event;
|
||||
rc = register_netdevice_notifier(ð_dev->netdevice_nb);
|
||||
if (rc) {
|
||||
ipa_eth_dev_err(eth_dev, "Failed to register netdev notifier");
|
||||
goto err_register;
|
||||
}
|
||||
|
||||
/* When registering with netdevice notifier, only REGISTER and UP events
|
||||
* are replayed. Fetch current link state; future link state updates
|
||||
* will be processed through CHANGE event.
|
||||
*/
|
||||
if (ipa_eth_net_update_link(eth_dev))
|
||||
ipa_eth_device_refresh_sched(eth_dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_register:
|
||||
eth_dev->netdevice_nb.notifier_call = NULL;
|
||||
err_net_dev:
|
||||
ipa_eth_nd_op(eth_dev, close_device, eth_dev);
|
||||
err_open:
|
||||
return rc;
|
||||
}
|
||||
|
||||
void ipa_eth_net_close_device(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
unregister_netdevice_notifier(ð_dev->netdevice_nb);
|
||||
eth_dev->netdevice_nb.notifier_call = NULL;
|
||||
|
||||
ipa_eth_nd_op(eth_dev, close_device, eth_dev);
|
||||
}
|
||||
|
||||
int ipa_eth_net_save_regs(struct ipa_eth_device *eth_dev)
|
||||
{
|
||||
struct ipa_eth_net_driver *nd = eth_dev->nd;
|
||||
|
||||
if (nd && nd->ops->save_regs)
|
||||
return ipa_eth_nd_op(eth_dev, save_regs, eth_dev, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
@ -1,611 +0,0 @@
|
|||
// 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 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;
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,838 +6,10 @@
|
|||
#ifndef _IPA_ETH_H_
|
||||
#define _IPA_ETH_H_
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ipc_logging.h>
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/netdev_features.h>
|
||||
|
||||
#include <linux/ipa.h>
|
||||
#include <linux/msm_ipa.h>
|
||||
#include <linux/msm_gsi.h>
|
||||
|
||||
/**
|
||||
* API Version Changes
|
||||
* ---------------------------------------------------------------------------
|
||||
* 1 - Initial version
|
||||
* 2 - API separation for use by network and offload drivers
|
||||
* - New ipa_eth_net_*() APIs offer safer interface for offload
|
||||
* drivers to call into ipa_eth_net_ops
|
||||
* - ipa_eth_net_ops.request_channel() to accept additional
|
||||
* memory allocation params, including custom memory allocator
|
||||
* defined via struct ipa_eth_dma_allocator interface
|
||||
* - probe() and remove() offload bus ops are replaced by pair()
|
||||
* and unpair() callbacks respectively
|
||||
* 3 - Added .save_regs() callback for network and offload drivers
|
||||
* 4 - Added ipa_eth_device_notify() interface for client drivers
|
||||
* to notify of various device events.
|
||||
* 5 - Removed ipa_eth_{gsi,uc}_iommu_*{} APIs that were used for
|
||||
* mapping memory to GSI and IPA uC IOMMU CBs.
|
||||
* 6 - Added ipa_eth_ep_deinit()
|
||||
* 7 - ipa_eth_net_ops.receive_skb() now accepts in_napi parameter
|
||||
* 8 - Added IPA rx/tx intf properties to ipa_eth_device
|
||||
*/
|
||||
|
||||
#define IPA_ETH_API_VER 8
|
||||
|
||||
/**
|
||||
* enum ipa_eth_dev_features - Features supported by an ethernet device or
|
||||
* driver that may be requested via offload APIs
|
||||
* @IPA_ETH_DEV_F_L2_CSUM_BIT: Ethernet L2 checksum offload
|
||||
* @IPA_ETH_DEV_F_L3_CSUM_BIT: Ethernet L2 checksum offload
|
||||
* @IPA_ETH_DEV_F_TCP_CSUM_BIT: TCP checksum offload
|
||||
* @IPA_ETH_DEV_F_UDP_CSUM_BIT: UDP checksum offload
|
||||
* @IPA_ETH_DEV_F_LSO_BIT: Large Send Offload / TCP Segmentation Offload
|
||||
* @IPA_ETH_DEV_F_LRO_BIT: Large Receive Offload / Receive Side Coalescing
|
||||
* @IPA_ETH_DEV_F_VLAN_BIT: VLAN Offload
|
||||
* @IPA_ETH_DEV_F_MODC_BIT: Counter based event moderation
|
||||
* @IPA_ETH_DEV_F_MODT_BIT: Timer based event moderation
|
||||
*
|
||||
* Ethernet hardware features represented as bit numbers below are used in
|
||||
* IPA_ETH_DEV_F_* feature flags that are to be used in offload APIs.
|
||||
*/
|
||||
enum ipa_eth_dev_features {
|
||||
IPA_ETH_DEV_F_L2_CSUM_BIT,
|
||||
IPA_ETH_DEV_F_L3_CSUM_BIT,
|
||||
IPA_ETH_DEV_F_TCP_CSUM_BIT,
|
||||
IPA_ETH_DEV_F_UDP_CSUM_BIT,
|
||||
IPA_ETH_DEV_F_LSO_BIT,
|
||||
IPA_ETH_DEV_F_LRO_BIT,
|
||||
IPA_ETH_DEV_F_VLAN_BIT,
|
||||
IPA_ETH_DEV_F_MODC_BIT,
|
||||
IPA_ETH_DEV_F_MODT_BIT,
|
||||
};
|
||||
|
||||
#define ipa_eth_dev_f(f) BIT(IPA_ETH_DEV_F_##f##_BIT)
|
||||
|
||||
#define IPA_ETH_DEV_F_L2_CSUM ipa_eth_dev_f(L2_CSUM)
|
||||
#define IPA_ETH_DEV_F_L3_CSUM ipa_eth_dev_f(L3_CSUM)
|
||||
#define IPA_ETH_DEV_F_TCP_CSUM ipa_eth_dev_f(TCP_CSUM)
|
||||
#define IPA_ETH_DEV_F_UDP_CSUM ipa_eth_dev_f(UDP_CSUM)
|
||||
#define IPA_ETH_DEV_F_LSO ipa_eth_dev_f(LSO)
|
||||
#define IPA_ETH_DEV_F_LRO ipa_eth_dev_f(LRO)
|
||||
#define IPA_ETH_DEV_F_VLAN ipa_eth_dev_f(VLAN)
|
||||
#define IPA_ETH_DEV_F_MODC ipa_eth_dev_f(MODC)
|
||||
#define IPA_ETH_DEV_F_MODT ipa_eth_dev_f(MODT)
|
||||
|
||||
/**
|
||||
* enum ipa_eth_dev_events - Events supported by an ethernet device that may be
|
||||
* requested via offload APIs
|
||||
* @IPA_ETH_DEV_EV_RX_INT_BIT: Rx interrupt
|
||||
* @IPA_ETH_DEV_EV_TX_INT_BIT: Tx interrupt
|
||||
* @IPA_ETH_DEV_EV_RX_PTR_BIT: Rx (head) pointer write-back
|
||||
* @IPA_ETH_DEV_EV_TX_PTR_BIT: Tx (head) pointer write-back
|
||||
*/
|
||||
enum ipa_eth_dev_events {
|
||||
IPA_ETH_DEV_EV_RX_INT_BIT,
|
||||
IPA_ETH_DEV_EV_TX_INT_BIT,
|
||||
IPA_ETH_DEV_EV_RX_PTR_BIT,
|
||||
IPA_ETH_DEV_EV_TX_PTR_BIT
|
||||
};
|
||||
|
||||
#define ipa_eth_dev_ev(ev) BIT(IPA_ETH_DEV_EV_##ev##_BIT)
|
||||
|
||||
#define IPA_ETH_DEV_EV_RX_INT ipa_eth_dev_ev(RX_INT)
|
||||
#define IPA_ETH_DEV_EV_TX_INT ipa_eth_dev_ev(TX_INT)
|
||||
#define IPA_ETH_DEV_EV_RX_PTR ipa_eth_dev_ev(RX_PTR)
|
||||
#define IPA_ETH_DEV_EV_TX_PTR ipa_eth_dev_ev(TX_PTR)
|
||||
|
||||
/**
|
||||
* enum ipa_eth_channel_dir - Direction of a ring / channel
|
||||
* @IPA_ETH_CH_DIR_RX: Traffic flowing from device to IPA
|
||||
* @IPA_ETH_CH_DIR_TX: Traffic flowing from IPA to device
|
||||
*/
|
||||
enum ipa_eth_channel_dir {
|
||||
IPA_ETH_CH_DIR_RX,
|
||||
IPA_ETH_CH_DIR_TX,
|
||||
};
|
||||
|
||||
#define IPA_ETH_DIR_RX IPA_ETH_CH_DIR_RX
|
||||
#define IPA_ETH_DIR_TX IPA_ETH_CH_DIR_TX
|
||||
|
||||
/**
|
||||
* enum ipa_eth_offload_state - Offload state of an ethernet device
|
||||
* @IPA_ETH_OF_ST_DEINITED: No offload path resources are allocated
|
||||
* @IPA_ETH_OF_ST_INITED: Offload path resources are allocated, but not started
|
||||
* @IPA_ETH_OF_ST_STARTED: Offload path is started and ready to handle traffic
|
||||
* @IPA_ETH_OF_ST_ERROR: One or more offload path components are in error state
|
||||
* @IPA_ETH_OF_ST_RECOVERY: Offload path is attempting to recover from error
|
||||
*/
|
||||
enum ipa_eth_offload_state {
|
||||
IPA_ETH_OF_ST_DEINITED = 0,
|
||||
IPA_ETH_OF_ST_INITED,
|
||||
IPA_ETH_OF_ST_STARTED,
|
||||
IPA_ETH_OF_ST_ERROR,
|
||||
IPA_ETH_OF_ST_RECOVERY,
|
||||
IPA_ETH_OF_ST_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum ipa_eth_offload_state - States of the network interface
|
||||
* @IPA_ETH_IF_ST_UP: Network interface is up in software/Linux
|
||||
* @IPA_ETH_IF_ST_LOWER_UP: Network interface PHY link is up / cable connected
|
||||
*/
|
||||
enum ipa_eth_interface_states {
|
||||
IPA_ETH_IF_ST_UP,
|
||||
IPA_ETH_IF_ST_LOWER_UP,
|
||||
IPA_ETH_IF_ST_MAX,
|
||||
};
|
||||
|
||||
struct ipa_eth_device;
|
||||
struct ipa_eth_net_driver;
|
||||
|
||||
/**
|
||||
* struct ipa_eth_resource - Memory based resource info
|
||||
* @size: Size of the memory accessible
|
||||
* @vaddr: Kernel mapped address of the resource
|
||||
* @daddr: DMA address of the resource
|
||||
* @paddr: Physical address of the resource
|
||||
*/
|
||||
struct ipa_eth_resource {
|
||||
size_t size;
|
||||
|
||||
void *vaddr;
|
||||
dma_addr_t daddr;
|
||||
phys_addr_t paddr;
|
||||
};
|
||||
|
||||
/**
|
||||
* ipa_eth_mem_it_t() - Callback used by the ipa_eth_dma_allocator.walk() as it
|
||||
* iterates through each contiguous chunk of memory
|
||||
* @eth_dev: Device to which the memory belong
|
||||
* @cmem: Contigous mapping. If cmem->paddr is NULL, it could be determined from
|
||||
* ipa_eth_dma_allocator.paddr(cmem->vaddr)
|
||||
* @arg: Private argument passed to ipa_eth_dma_allocator.walk() that can hold
|
||||
* necessary context required by the iterator
|
||||
*
|
||||
* See &struct ipa_eth_dma_allocator for more details.
|
||||
*/
|
||||
typedef int (*ipa_eth_mem_it_t)(struct ipa_eth_device *eth_dev,
|
||||
const struct ipa_eth_resource *cmem, void *arg);
|
||||
|
||||
/**
|
||||
* struct ipa_eth_dma_allocator - Custom DMA memory allocator interface
|
||||
* @name: Name of the allocator
|
||||
*/
|
||||
struct ipa_eth_dma_allocator {
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* .paddr() - Convert a virtual address previously allocated by this
|
||||
* ipa_eth_dma_allocator, to physical address
|
||||
* @eth_dev: Device which owns the memory
|
||||
* @vaddr: Kernel mapped address of the resource
|
||||
*
|
||||
* Return: Physical address of @vaddr
|
||||
*/
|
||||
phys_addr_t (*paddr)(struct ipa_eth_device *eth_dev,
|
||||
const void *vaddr);
|
||||
|
||||
/**
|
||||
* .alloc() - Allocates DMA memory for a given device
|
||||
* @eth_dev: Device which will own the memory. Note that @eth_dev->dev
|
||||
* should be a valid struct device pointer.
|
||||
* @size: Minimum number of bytes to allocate
|
||||
* @gfp: Kernel GFP_* flags to use, if the allocator supports it
|
||||
* @mem: Memory info if the allocation was successful. @mem->paddr may
|
||||
* be NUll or not valid, use ipa_eth_dma_allocator.paddr() to get
|
||||
* the correct physical address for a page of virtual address.
|
||||
*
|
||||
* This API callback is typically called by the network driver to
|
||||
* allocate memory for descriptor rings, buffers, etc. Any memory
|
||||
* allocated by this callback should be traversable by the .remap()
|
||||
* API callback implementation.
|
||||
*
|
||||
* Return: 0 on success, non-zero otherwise
|
||||
*/
|
||||
int (*alloc)(struct ipa_eth_device *eth_dev, size_t size, gfp_t gfp,
|
||||
struct ipa_eth_resource *mem);
|
||||
|
||||
/**
|
||||
* .free() - Free a memory previously allocated using .alloc() API
|
||||
* @eth_dev: Device which owns the memory
|
||||
* @mem: Memory info
|
||||
*
|
||||
* This API callback is typically called by the network driver to free
|
||||
* memory resources associated with descriptor rings, buffers, etc. once
|
||||
* their associated hardware queues are stopped.
|
||||
*/
|
||||
void (*free)(struct ipa_eth_device *eth_dev,
|
||||
struct ipa_eth_resource *mem);
|
||||
|
||||
/**
|
||||
* .walk() - Iterates over memory previously allocated by .alloc()
|
||||
* @eth_dev: Device which owns the memory
|
||||
* @mem: Allocated memory that need to be iterated over
|
||||
* @it: Iterator that is invoked for each chunk (typically PAGE_SIZE)
|
||||
* of memory
|
||||
* @arg: Private argument passed to @it for each invocation
|
||||
*
|
||||
* This API is expected to iterate over pieces of an allocated memory
|
||||
* for typical purposes remapping to additional peripherals, and later
|
||||
* unmapping from them. .walk() is responsible to invoke the iterator
|
||||
* @it for each mappable region within an allocated space, which is
|
||||
* typically page sized.
|
||||
*
|
||||
* Iteration is expected to stop either when the page walk completes or
|
||||
* when any of the @it() invocations return failure. Iterator can add
|
||||
* additional checks for memory alignments, addressability, etc. that
|
||||
* can also fail. In any case, the API is expected to return the number
|
||||
* of bytes (<= mem->size) from the given memory that was successfully
|
||||
* iterated. Although the API may re-align memory regions while invoking
|
||||
* the iterator, it should still return only the number of bytes of the
|
||||
* original memory that was successfully iterated.
|
||||
*
|
||||
* .walk() API could be used for any purpose of iteration, not limited
|
||||
* to memory mapping and unmapping. Implementation of the API should be
|
||||
* flexible for generalized use-cases. Caller of this API should always
|
||||
* check for the return value against @mem->size to make sure if the
|
||||
* intended operation was successful, and explicitly revert any partial
|
||||
* operation. Ex,
|
||||
*
|
||||
* mapped_bytes = alctr->walk(dev, mem_region, iommu_mapper, map_ctx);
|
||||
* if (mapped_bytes != mem_region->size) {
|
||||
* struct ipa_eth_resource unmap_region = *mem_region;
|
||||
* unmap_region.size = mapped_bytes;
|
||||
* alctr->walk(dev, unmap_region, smmu_map, ctx);
|
||||
* return -ENOMEM;
|
||||
* }
|
||||
*
|
||||
* Return: the number of bytes in @mem that were successfully iterated
|
||||
*/
|
||||
size_t (*walk)(struct ipa_eth_device *eth_dev,
|
||||
const struct ipa_eth_resource *mem,
|
||||
ipa_eth_mem_it_t it, void *arg);
|
||||
};
|
||||
|
||||
/**
|
||||
* enum ipa_eth_hw_type - Types of hardware used in an offload path
|
||||
* @IPA_ETH_HW_UC: IPA uC
|
||||
* @IPA_ETH_HW_GSI: GSI
|
||||
* @IPA_ETH_HW_IPA: IPA hardware/endpoint
|
||||
*/
|
||||
enum ipa_eth_hw_type {
|
||||
IPA_ETH_HW_UC,
|
||||
IPA_ETH_HW_GSI,
|
||||
IPA_ETH_HW_IPA,
|
||||
IPA_ETH_HW_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_hw_map_param - Params for mapping memory to IPA hardware
|
||||
* @map: If true, perform mapping of memory to the given hardware
|
||||
* @sym: If true, performs symmetric mapping where IO virtual address (IOVA)
|
||||
* used is the same as the one used on original device.
|
||||
* @read: Memory should be readable by hardware
|
||||
* @write: Memory should be writable by hardware
|
||||
*/
|
||||
struct ipa_eth_hw_map_param {
|
||||
bool map;
|
||||
bool sym;
|
||||
bool read;
|
||||
bool write;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_desc_params - Params for allocating descriptor memory
|
||||
* @size: Size of each descriptor. This field is usually filled in by the
|
||||
* network driver.
|
||||
* @count: Number of descriptors to be allocated
|
||||
* @allocator: DMA allocator to be used for IO memory allocation and mapping
|
||||
* @hw_map_params: Info on memory mapping requirements to IPA CBs
|
||||
*/
|
||||
struct ipa_eth_desc_params {
|
||||
size_t size;
|
||||
size_t count;
|
||||
struct ipa_eth_dma_allocator *allocator;
|
||||
struct ipa_eth_hw_map_param hw_map_params[IPA_ETH_HW_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_buff_params - Params for allocating buffer memory
|
||||
* @size: Size of data buffer associated with a descriptor
|
||||
* @count: Number of buffers. This ield is usually filled in by the network
|
||||
* driver and is typically the same value as descriptor count.
|
||||
* @allocator: DMA allocator to be used for IO memory allocation and mapping
|
||||
* @maps: Info on memory mapping requirements to IPA CBs
|
||||
*/
|
||||
struct ipa_eth_buff_params {
|
||||
size_t size;
|
||||
size_t count;
|
||||
struct ipa_eth_dma_allocator *allocator;
|
||||
struct ipa_eth_hw_map_param hw_map_params[IPA_ETH_HW_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_channel_mem_params - Params for various channel memory
|
||||
* @desc: Descriptor memory parameters
|
||||
* @buff: Buffer memory parameters
|
||||
*/
|
||||
struct ipa_eth_channel_mem_params {
|
||||
struct ipa_eth_desc_params desc;
|
||||
struct ipa_eth_buff_params buff;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_channel_mem - Represents a piece of memory used by the
|
||||
* network device channel for descriptrors, buffers, etc.
|
||||
* @mem_list_entry: list entry in either of ipa_eth_channel.desc_mem or
|
||||
* ipa_eth_channel.buff_mem
|
||||
* @mem: Memory mapping info as used by the network device/driver
|
||||
* @cb_mem: Memory mapping info as used by each of IPA context banks. When a
|
||||
* mapping is present, cb_mem[cb_type].size would be non-zero.
|
||||
*/
|
||||
struct ipa_eth_channel_mem {
|
||||
struct list_head mem_list_entry;
|
||||
|
||||
struct ipa_eth_resource mem;
|
||||
|
||||
/* private: for internal use by offload sub-system */
|
||||
struct ipa_eth_resource *cb_mem;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_channel - Represents a network device channel
|
||||
* @channel_list: list entry in either of ipa_eth_device.rx_channels or
|
||||
* ipa_eth_device.tx_channels
|
||||
* @nd_priv: Private field for use by network driver
|
||||
* @events: Events supported by the channel
|
||||
* @features: Features enabled in the channel
|
||||
* @direction: Channel direction
|
||||
* @mem_params: Channel memory params filled in collectively by Offload driver,
|
||||
* Network driver and Offload sub-system
|
||||
* @queue: Network device queue/ring number
|
||||
* @desc_mem: Descriptor ring memory list
|
||||
* @buff_mem: Data buffer memory list
|
||||
* @od_priv: Private field for use by offload driver
|
||||
* @eth_dev: Associated ipa_eth_device
|
||||
* @ipa_client: IPA client type enum to be used for the channel
|
||||
* @ipa_ep_num: IPA endpoint number configured for the client type
|
||||
* @process_skb: Callback to be called for processing IPA exception
|
||||
* packets received from the IPA endpoint
|
||||
* @ipa_priv: Private field to be used by offload subsystem
|
||||
* @exception_total: Total number of exception packets received
|
||||
* @exception_drops: Total number of dropped exception packets
|
||||
* @exception_loopback: Total number of exception packets looped back
|
||||
* into IPA
|
||||
*/
|
||||
struct ipa_eth_channel {
|
||||
struct list_head channel_list;
|
||||
|
||||
/* fields managed by network driver */
|
||||
void *nd_priv;
|
||||
|
||||
unsigned long events;
|
||||
unsigned long features;
|
||||
enum ipa_eth_channel_dir direction;
|
||||
struct ipa_eth_channel_mem_params mem_params;
|
||||
|
||||
int queue;
|
||||
struct list_head desc_mem;
|
||||
struct list_head buff_mem;
|
||||
|
||||
/* fields managed by offload driver */
|
||||
void *od_priv;
|
||||
struct ipa_eth_device *eth_dev;
|
||||
|
||||
enum ipa_client_type ipa_client;
|
||||
int ipa_ep_num;
|
||||
|
||||
int (*process_skb)(struct ipa_eth_channel *ch, struct sk_buff *skb);
|
||||
|
||||
/* fields managed by offload subsystem */
|
||||
void *ipa_priv;
|
||||
|
||||
u64 exception_total;
|
||||
u64 exception_drops;
|
||||
u64 exception_loopback;
|
||||
};
|
||||
|
||||
#define IPA_ETH_CH_IS_RX(ch) ((ch)->direction == IPA_ETH_CH_DIR_RX)
|
||||
#define IPA_ETH_CH_IS_TX(ch) ((ch)->direction == IPA_ETH_CH_DIR_TX)
|
||||
|
||||
/**
|
||||
* struct ipa_eth_device - Represents an ethernet device
|
||||
* @device_list: Entry in the global offload device list
|
||||
* @bus_device_list: Entry in the per-bus offload device list
|
||||
* @net_dev: Netdev registered by the network driver
|
||||
* @nd_priv: Private field for use by network driver
|
||||
* @od_priv: Private field for use by offload driver
|
||||
* @rx_channels: Rx channels allocated for the offload path
|
||||
* @tx_channels: Tx channels allocated for the offload path
|
||||
* @ipa_rx_intf: IPA properties Rx interface
|
||||
* @ipa_tx_intf: IPA properties Tx interface
|
||||
* @of_state: Offload state of the device
|
||||
* @dev: Pointer to struct device
|
||||
* @nd: IPA offload net driver associated with the device
|
||||
* @od: IPA offload driver that is managing the device
|
||||
* @netdevice_nb: Notifier block for receiving netdev events from the
|
||||
* network device (to monitor link state changes)
|
||||
* @init: Allowed to initialize offload path for the device
|
||||
* @start: Allowed to start offload data path for the device
|
||||
* @start_on_wakeup: Allow start upon wake up by device
|
||||
* @start_on_resume: Allow start upon driver resume
|
||||
* @start_on_timeout: Timeout in milliseconds after which @start is enabled
|
||||
* @start_timer: Timer associated with @start_on_timer
|
||||
* @flags: Device flags
|
||||
* @if_state: Interface state - one or more bit numbers IPA_ETH_IF_ST_*
|
||||
* @pm_handle: IPA PM client handle for the device
|
||||
* @phdr_v4_handle: Partial header handle for IPv4
|
||||
* @phdr_v6_handle: Partial header handle for IPv6
|
||||
* @bus_priv: Private field for use by offload subsystem bus layer
|
||||
* @ipa_priv: Private field for use by offload subsystem
|
||||
* @debugfs: Debugfs root for the device
|
||||
* @refresh: Work struct used to perform device refresh
|
||||
*/
|
||||
struct ipa_eth_device {
|
||||
struct list_head device_list;
|
||||
struct list_head bus_device_list;
|
||||
|
||||
/* fields managed by the network driver */
|
||||
struct net_device *net_dev;
|
||||
void *nd_priv;
|
||||
|
||||
/* fields managed by offload driver */
|
||||
void *od_priv;
|
||||
|
||||
/* fields managed by offload subsystem */
|
||||
struct list_head rx_channels;
|
||||
struct list_head tx_channels;
|
||||
|
||||
struct ipa_rx_intf ipa_rx_intf;
|
||||
struct ipa_tx_intf ipa_tx_intf;
|
||||
|
||||
enum ipa_eth_offload_state of_state;
|
||||
|
||||
struct device *dev;
|
||||
struct ipa_eth_net_driver *nd;
|
||||
struct ipa_eth_offload_driver *od;
|
||||
|
||||
struct notifier_block netdevice_nb;
|
||||
|
||||
bool init;
|
||||
bool start;
|
||||
|
||||
bool start_on_wakeup;
|
||||
bool start_on_resume;
|
||||
u32 start_on_timeout;
|
||||
struct timer_list start_timer;
|
||||
|
||||
unsigned long flags;
|
||||
unsigned long if_state;
|
||||
|
||||
u32 pm_handle;
|
||||
u32 phdr_v4_handle;
|
||||
u32 phdr_v6_handle;
|
||||
|
||||
void *bus_priv;
|
||||
void *ipa_priv;
|
||||
struct dentry *debugfs;
|
||||
|
||||
struct work_struct refresh;
|
||||
};
|
||||
|
||||
/**
|
||||
* enum ipa_eth_device_event - Events related to device state
|
||||
* @IPA_ETH_DEV_RESET_PREPARE: Device is entering reset and is requesting
|
||||
* offload path to stop using the device
|
||||
* @IPA_ETH_DEV_RESET_COMPLETE: Device has completed resetting and is
|
||||
* requesting offload path to resume its operations
|
||||
* @IPA_ETH_DEV_ADD_MACSEC_IF: A MACSec interface is coming up
|
||||
* @IPA_ETH_DEV_DEL_MACSEC_IF: A MACSec interface is going down
|
||||
*/
|
||||
enum ipa_eth_device_event {
|
||||
IPA_ETH_DEV_RESET_PREPARE,
|
||||
IPA_ETH_DEV_RESET_COMPLETE,
|
||||
IPA_ETH_DEV_ADD_MACSEC_IF,
|
||||
IPA_ETH_DEV_DEL_MACSEC_IF,
|
||||
IPA_ETH_DEV_EVENT_COUNT,
|
||||
};
|
||||
|
||||
int ipa_eth_device_notify(struct ipa_eth_device *eth_dev,
|
||||
enum ipa_eth_device_event event, void *data);
|
||||
|
||||
/**
|
||||
* struct ipa_eth_net_ops - Network device operations required for IPA offload
|
||||
*/
|
||||
struct ipa_eth_net_ops {
|
||||
/**
|
||||
* .open_device() - Initialize the network device if needed and fill in
|
||||
* @eth_dev->net_dev field
|
||||
* @eth_dev: Device to initialize
|
||||
*
|
||||
* Some network perform complete device initialization only in driver
|
||||
* .ndo_open(). Offload sub-system may try to use the network hardware
|
||||
* for offload path even before .ndo_open() is called. .open_device() is
|
||||
* expected to perform all device initialization that may be required
|
||||
* to make the hardware functional for offload path, irrespective of
|
||||
* whether .ndo_open() gets called.
|
||||
*
|
||||
* Once the device is initialized, .open_device() should initialize the
|
||||
* @eth_dev->net_dev field with the driver struct net_device pointer
|
||||
* before returning.
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*open_device)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .close_device() Deinitialize the device if required
|
||||
* @eth_dev: Device to deinitialize
|
||||
*
|
||||
* This is called when the offload data path is deinitialized and the
|
||||
* offload subsystem do not see any offload drivers registered to
|
||||
* handle the device anymore. Typically the device is maintained in
|
||||
* the initialized state until both Linux and IPA data paths are
|
||||
* deinitialized.
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
void (*close_device)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .save_regs() - Save registers for debugging
|
||||
* @eth_dev: Offloaded device
|
||||
* @regs: if not NULL, write saved data address to the given pointer
|
||||
* @size: if not NULL, write the size of saved data to the given pointer
|
||||
*
|
||||
* Return: 0 on success, errno otherwise.
|
||||
*/
|
||||
int (*save_regs)(struct ipa_eth_device *eth_dev,
|
||||
void **regs, size_t *size);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_net_driver - Network driver to be registered with IPA offload
|
||||
* subsystem
|
||||
* @driver_list: Entry in the offload sub-system network driver list
|
||||
* @bus_driver_list: Entry in the bus specific driver list
|
||||
* @ops: Network device operations
|
||||
* @bus_priv: Bus private data
|
||||
* @name: Name of the network driver
|
||||
* @bus: Pointer to the bus object. Must use &pci_bus_type for PCI and
|
||||
* &platform_bus_type for platform drivers. This property is used by the
|
||||
* offload subsystem to deduce the network driver's original pci_driver
|
||||
* or plaform_driver object from the @driver argument during driver
|
||||
* registration.
|
||||
* Beyond registration, the bus type is also used to deduce a pci_dev or
|
||||
* platform_device object from a `struct device` pointer.
|
||||
* @driver: Pointer to the device_driver object embedded within a PCI/plaform
|
||||
* driver object used by the network driver
|
||||
* @events: Events supported by the network device
|
||||
* @features: Capabilities of the network device
|
||||
* @debugfs: Debugfs directory for the device
|
||||
*/
|
||||
struct ipa_eth_net_driver {
|
||||
struct list_head driver_list;
|
||||
struct list_head bus_driver_list;
|
||||
|
||||
struct ipa_eth_net_ops *ops;
|
||||
void *bus_priv;
|
||||
|
||||
const char *name;
|
||||
struct bus_type *bus;
|
||||
struct device_driver *driver;
|
||||
|
||||
unsigned long events;
|
||||
unsigned long features;
|
||||
|
||||
struct dentry *debugfs;
|
||||
};
|
||||
|
||||
int ipa_eth_register_net_driver(struct ipa_eth_net_driver *nd);
|
||||
void ipa_eth_unregister_net_driver(struct ipa_eth_net_driver *nd);
|
||||
|
||||
/**
|
||||
* struct ipa_eth_offload_link_stats - Stats for each link within an
|
||||
* offload data path
|
||||
* @valid: Stats are not available for the link
|
||||
* @events: Number of events reported to the link
|
||||
* @frames: Number of frames received by the link
|
||||
* @packets: Number of packets received by the link
|
||||
* @octets: Number of bytes received by the link
|
||||
*/
|
||||
struct ipa_eth_offload_link_stats {
|
||||
bool valid;
|
||||
u64 events;
|
||||
u64 frames;
|
||||
u64 packets;
|
||||
u64 octets;
|
||||
};
|
||||
|
||||
struct ipa_eth_offload_ch_stats {
|
||||
struct ipa_eth_offload_link_stats ndev;
|
||||
struct ipa_eth_offload_link_stats host;
|
||||
struct ipa_eth_offload_link_stats uc;
|
||||
struct ipa_eth_offload_link_stats gsi;
|
||||
struct ipa_eth_offload_link_stats ipa;
|
||||
};
|
||||
|
||||
struct ipa_eth_offload_stats {
|
||||
struct ipa_eth_offload_ch_stats rx;
|
||||
struct ipa_eth_offload_ch_stats tx;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_offload_ops - Offload operations provided by an offload driver
|
||||
*/
|
||||
struct ipa_eth_offload_ops {
|
||||
/**
|
||||
* .pair() - Pair a new device, if compatible, with the offload driver
|
||||
* @eth_dev: Device to pair with the offload driver
|
||||
*
|
||||
* This API is called by offload sub-system in order to pair an ethernet
|
||||
* device with the offload driver. Typically this API is called soon
|
||||
* after the device probe is completed and registered with the offload
|
||||
* sub-system. When the API is called, offload driver is expected to
|
||||
* check if the device is compatible with the driver and that the driver
|
||||
* has enough offload resources for offloading the device to IPA.
|
||||
*
|
||||
* The API implementation can expect the eth_dev->dev to have been
|
||||
* already initialized by the offload subsystem, from which a bus
|
||||
* specific device pointer (pci_dev, platform_device, etc.) can be
|
||||
* derived. The API is expected to perform at least the following:
|
||||
*
|
||||
* 1. Ensure the device is compatible with the offload driver. For
|
||||
* plaform drivers, the .compatible DT property could be used while
|
||||
* PCI IDs could be used for matching with a PCI device.
|
||||
* 2. If multiple network devices of the same type can be managed by the
|
||||
* offload driver, it may attempt to pair @eth_dev with an available
|
||||
* resource set for a single instance.
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*pair)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .unpair() - Unpair a device from the offload driver
|
||||
* @eth_dev: Device to unpair
|
||||
*
|
||||
* Unpairing the device from offload driver should make sure that all
|
||||
* resources allocated for the device are freed and the data path is
|
||||
* stopped and deinitialized, and device is readied for removal. The
|
||||
* implementation should be able to handle plug-n-play devices by not
|
||||
* assuming the device to be connected anymore to the bus/system when
|
||||
* this API is called.
|
||||
*/
|
||||
void (*unpair)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .init_tx() - Initialize offload path in Tx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*init_tx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .start_tx() - Start offload path in Tx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*start_tx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .stop_tx() - Stop offload path in Tx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*stop_tx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .deinit_tx() - Deinitialize offload path in Tx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*deinit_tx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .init_rx() - Initialize offload path in Rx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*init_rx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .start_rx() - Start offload path in Rx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*start_rx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .stop_rx() - Stop offload path in Rx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*stop_rx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .deinit_rx() - Deinitialize offload path in Rx direction
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*deinit_rx)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .get_stats() - Get offload statistics
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*get_stats)(struct ipa_eth_device *eth_dev,
|
||||
struct ipa_eth_offload_stats *stats);
|
||||
|
||||
/**
|
||||
* .clear_stats() - Clear offload statistics
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise
|
||||
*/
|
||||
int (*clear_stats)(struct ipa_eth_device *eth_dev);
|
||||
|
||||
/**
|
||||
* .save_regs() - Save registers for debugging
|
||||
* @eth_dev: Offloaded device
|
||||
* @regs: if not NULL, write saved data address to the given pointer
|
||||
* @size: if not NULL, write the size of saved data to the given pointer
|
||||
*
|
||||
* Return: 0 on success, errno otherwise.
|
||||
*/
|
||||
int (*save_regs)(struct ipa_eth_device *eth_dev,
|
||||
void **regs, size_t *size);
|
||||
|
||||
/**
|
||||
* .prepare_reset() - Prepare offload path for netdev reset
|
||||
* @eth_dev: Offloaded device
|
||||
* @data: Private data the network driver has provided
|
||||
*
|
||||
* Return: 0 on success, errno otherwise.
|
||||
*/
|
||||
int (*prepare_reset)(struct ipa_eth_device *eth_dev, void *data);
|
||||
|
||||
/**
|
||||
* .complete_reset() - Netdev reset completed, offload path can resume
|
||||
* @eth_dev: Offloaded device
|
||||
* @data: Private data the network driver has provided
|
||||
*
|
||||
* Return: 0 on success, errno otherwise.
|
||||
*/
|
||||
int (*complete_reset)(struct ipa_eth_device *eth_dev, void *data);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ipa_eth_offload_driver - Offload driver to be registered with the
|
||||
* offload sub-system
|
||||
* @driver_list: Entry in the global offload driver list
|
||||
* @mutex: Mutex to protect offload driver struct
|
||||
* @name: Name of the offload driver
|
||||
* @bus: Supported network device bus type
|
||||
* @ops: Offload operations
|
||||
* @bus_ops: Bus level callbacks and operations for the offload driver
|
||||
* @debugfs: Debugfs directory for the offload driver
|
||||
*/
|
||||
struct ipa_eth_offload_driver {
|
||||
struct list_head driver_list;
|
||||
struct mutex mutex;
|
||||
|
||||
const char *name;
|
||||
struct bus_type *bus;
|
||||
|
||||
struct ipa_eth_offload_ops *ops;
|
||||
|
||||
struct dentry *debugfs;
|
||||
};
|
||||
|
||||
int ipa_eth_register_offload_driver(struct ipa_eth_offload_driver *od);
|
||||
void ipa_eth_unregister_offload_driver(struct ipa_eth_offload_driver *od);
|
||||
|
||||
/* IPC logging interface */
|
||||
|
||||
#define ipa_eth_ipc_do_log(ipcbuf, fmt, args...) \
|
||||
do { \
|
||||
void *__buf = (ipcbuf); \
|
||||
ipc_log_string(__buf, " %s:%d " fmt "\n", \
|
||||
__func__, __LINE__, ## args); \
|
||||
} while (0)
|
||||
|
||||
#define ipa_eth_ipc_log(fmt, args...) \
|
||||
do { \
|
||||
void *ipa_eth_get_ipc_logbuf(void); \
|
||||
ipa_eth_ipc_do_log(ipa_eth_get_ipc_logbuf(), \
|
||||
fmt, ## args); \
|
||||
} while (0)
|
||||
|
||||
#define ipa_eth_ipc_dbg(fmt, args...) \
|
||||
do { \
|
||||
void *ipa_eth_get_ipc_logbuf_dbg(void); \
|
||||
ipa_eth_ipc_do_log(ipa_eth_get_ipc_logbuf_dbg(), \
|
||||
fmt, ## args); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/* New architecture prototypes */
|
||||
|
||||
typedef void (*ipa_eth_ready_cb)(void *user_data);
|
||||
|
|
|
|||
Loading…
Reference in a new issue