Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2c9598d authored by Jinesh K. Jayakumar's avatar Jinesh K. Jayakumar Committed by Gerrit - the friendly Code Review server
Browse files

msm: ipa: eth: Register upper interfaces with IPA



In certain modes of IPA operation (Ex. VLAN, MACSec, etc.), the upper
interface can be chosen by user space as the primary network device for
LAN management. Register IPA interface properties for immediate upper
interfaces in addition to the real interface.

Change-Id: I2b8c9f9e1ccfddaa0ff23fff16c331f0378ac1f3
Signed-off-by: default avatarJinesh K. Jayakumar <jineshk@codeaurora.org>
parent c67cb625
Loading
Loading
Loading
Loading
+34 −7
Original line number Diff line number Diff line
/* Copyright (c) 2019 The Linux Foundation. All rights reserved.
/* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -190,6 +190,13 @@ static int ipa_eth_start_device(struct ipa_eth_device *eth_dev)
		return rc;
	}

	/* We cannot register the interface during offload init phase because
	 * it will cause IPACM to install IPA filter rules when it receives a
	 * link-up netdev event, even if offload path is not started and/or no
	 * ECM_CONNECT event is received from the driver. Since installing IPA
	 * filter rules while offload path is stopped can cause DL data stall,
	 * register the interface only after the offload path is started.
	 */
	rc = ipa_eth_ep_register_interface(eth_dev);
	if (rc) {
		ipa_eth_dev_err(eth_dev, "Failed to register EP interface");
@@ -299,12 +306,20 @@ static void ipa_eth_device_refresh(struct ipa_eth_device *eth_dev)
			return;
		}

		if (ipa_eth_net_watch_upper(eth_dev))
			ipa_eth_dev_err(eth_dev,
					"Failed to watch upper interfaces");

		if (ipa_eth_pm_vote_bw(eth_dev))
			ipa_eth_dev_err(eth_dev,
					"Failed to vote for required BW");
	} else {
		ipa_eth_dev_log(eth_dev, "Start is disallowed for the device");

		if (ipa_eth_net_unwatch_upper(eth_dev))
			ipa_eth_dev_err(eth_dev,
					"Failed to unwatch upper interfaces");

		if (eth_dev->of_state == IPA_ETH_OF_ST_STARTED) {
			IPA_ACTIVE_CLIENTS_INC_SIMPLE();
			ipa_eth_stop_device(eth_dev);
@@ -439,7 +454,7 @@ int ipa_eth_device_notify(struct ipa_eth_device *eth_dev,
		rc = ipa_eth_device_complete_reset(eth_dev, data);
		break;
	default:
		ipa_eth_dev_bug(eth_dev, "Unknown event");
		ipa_eth_dev_log(eth_dev, "Skipped event processing");
		break;
	}

@@ -504,16 +519,23 @@ struct ipa_eth_device *ipa_eth_alloc_device(
	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 = devm_kzalloc(dev, sizeof(*eth_dev), GFP_KERNEL);
	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;

@@ -531,6 +553,13 @@ struct ipa_eth_device *ipa_eth_alloc_device(

	eth_dev->init = eth_dev->start = !ipa_eth_noauto;

	/* Initialize private data */

	mutex_init(&ipa_priv->upper_mutex);
	INIT_LIST_HEAD(&ipa_priv->upper_devices);

	eth_dev->ipa_priv = ipa_priv;

	return eth_dev;
}

@@ -541,10 +570,8 @@ struct ipa_eth_device *ipa_eth_alloc_device(
 */
void ipa_eth_free_device(struct ipa_eth_device *eth_dev)
{
	struct device *dev = eth_dev->dev;

	memset(eth_dev, 0, sizeof(*eth_dev));
	devm_kfree(dev, eth_dev);
	kzfree(eth_dev->ipa_priv);
	kzfree(eth_dev);
}

/*
+213 −30
Original line number Diff line number Diff line
/* Copyright (c) 2019 The Linux Foundation. All rights reserved.
/* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -228,11 +228,16 @@ static void ipa_eth_ep_init_tx_props(

static int ipa_eth_ep_init_tx_intf(
		struct ipa_eth_device *eth_dev,
		struct ipa_tx_intf *tx_intf,
		bool vlan_mode)
{
	u32 num_props;
	struct ipa_eth_channel *ch;
	struct ipa_tx_intf *tx_intf = &eth_dev->ipa_tx_intf;

	if (tx_intf->prop) {
		ipa_eth_dev_bug(eth_dev, "IPA interface Tx prop is not empty");
		return -EFAULT;
	}

	/* one each for IPv4 and IPv6 */
	num_props = __list_size(&eth_dev->tx_channels) * 2;
@@ -257,11 +262,16 @@ static int ipa_eth_ep_init_tx_intf(

static int ipa_eth_ep_init_rx_intf(
		struct ipa_eth_device *eth_dev,
		struct ipa_rx_intf *rx_intf,
		bool vlan_mode)
{
	u32 num_props;
	struct ipa_eth_channel *ch;
	struct ipa_rx_intf *rx_intf = &eth_dev->ipa_rx_intf;

	if (rx_intf->prop) {
		ipa_eth_dev_bug(eth_dev, "IPA interface Rx prop is not empty");
		return -EFAULT;
	}

	/* one each for IPv4 and IPv6 */
	num_props = __list_size(&eth_dev->rx_channels) * 2;
@@ -284,51 +294,158 @@ static int ipa_eth_ep_init_rx_intf(
	return 0;
}

/**
 * ipa_eth_ep_register_interface() - Set Rx and Tx properties and register the
 *                                   interface with IPA
 * @eth_dev: Offload device to register
 *
 * Register a logical interface with IPA. The API expects netdev and channels
 * allocated prior to being called.
 *
 * Return: 0 on success, negative errno code otherwise
 */
int ipa_eth_ep_register_interface(struct ipa_eth_device *eth_dev)
static void ipa_eth_ep_deinit_interfaces(struct ipa_eth_device *eth_dev)
{
	kzfree(eth_dev->ipa_tx_intf.prop);
	memset(&eth_dev->ipa_tx_intf, 0, sizeof(eth_dev->ipa_tx_intf));

	kzfree(eth_dev->ipa_rx_intf.prop);
	memset(&eth_dev->ipa_rx_intf, 0, sizeof(eth_dev->ipa_rx_intf));

}

static int ipa_eth_ep_init_interfaces(struct ipa_eth_device *eth_dev)
{
	int rc;
	bool vlan_mode;
	struct ipa_tx_intf tx_intf;
	struct ipa_rx_intf rx_intf;

	memset(&tx_intf, 0, sizeof(tx_intf));
	memset(&rx_intf, 0, sizeof(rx_intf));
	if (eth_dev->ipa_rx_intf.num_props || eth_dev->ipa_tx_intf.num_props) {
		ipa_eth_dev_err(eth_dev, "Interface properties already exist");
		return -EFAULT;
	}

	rc = ipa3_is_vlan_mode(IPA_VLAN_IF_ETH, &vlan_mode);
	if (rc) {
		ipa_eth_dev_err(eth_dev, "Could not determine IPA VLAN mode");
		goto free_and_exit;
		return rc;
	}

	rc = ipa_eth_ep_init_tx_intf(eth_dev, &tx_intf, vlan_mode);
	rc = ipa_eth_ep_init_tx_intf(eth_dev, vlan_mode);
	if (rc)
		goto free_and_exit;

	rc = ipa_eth_ep_init_rx_intf(eth_dev, &rx_intf, vlan_mode);
	rc = ipa_eth_ep_init_rx_intf(eth_dev, vlan_mode);
	if (rc)
		goto free_and_exit;

	rc = ipa_register_intf(eth_dev->net_dev->name, &tx_intf, &rx_intf);
	if (!rc)
		ipa_eth_send_msg_connect(eth_dev);
	return 0;

free_and_exit:
	kzfree(tx_intf.prop);
	kzfree(rx_intf.prop);
	ipa_eth_ep_deinit_interfaces(eth_dev);

	return rc;
}

static int ipa_eth_ep_deregister_ipa_intf(
	struct net_device *net_dev)
{
	int rc;

	ipa_eth_log("Deregistering IPA intf %s", net_dev->name);

	rc = ipa_deregister_intf(net_dev->name);
	if (rc) {
		ipa_eth_err("Failed to deregister IPA intf %s", net_dev->name);
		return rc;
	}

	rc = ipa_eth_send_msg_disconnect(net_dev);
	if (rc)
		ipa_eth_err("Failed to send disconnect message", net_dev->name);

	return rc;
}

static int ipa_eth_ep_register_ipa_intf(
	struct net_device *net_dev,
	struct ipa_tx_intf *tx_intf,
	struct ipa_rx_intf *rx_intf)
{
	int rc;

	ipa_eth_log("Registering IPA intf %s", net_dev->name);

	rc = ipa_register_intf(net_dev->name, tx_intf, rx_intf);
	if (rc) {
		ipa_eth_err("Failed to register IPA intf %s", net_dev->name);
		return rc;
	}

	rc = ipa_eth_send_msg_connect(net_dev);
	if (rc) {
		ipa_eth_err("Failed to send connect message", net_dev->name);
		(void) ipa_eth_ep_deregister_ipa_intf(net_dev);
	}

	return rc;
}

static int ipa_eth_ep_register_alt_interface(
		struct ipa_eth_device *eth_dev,
		struct net_device *net_dev)
{
	struct ipa_tx_intf *tx_intf = &eth_dev->ipa_tx_intf;
	struct ipa_rx_intf *rx_intf = &eth_dev->ipa_rx_intf;

	return ipa_eth_ep_register_ipa_intf(net_dev, tx_intf, rx_intf);
}

static int ipa_eth_ep_deregister_alt_interface(
		struct ipa_eth_device *eth_dev,
		struct net_device *net_dev)
{
	return ipa_eth_ep_deregister_ipa_intf(net_dev);
}

static int __ipa_eth_ep_register_interface(
	struct ipa_eth_device *eth_dev)
{
	int rc;
	struct net_device *net_dev = eth_dev->net_dev;
	struct ipa_tx_intf *tx_intf = &eth_dev->ipa_tx_intf;
	struct ipa_rx_intf *rx_intf = &eth_dev->ipa_rx_intf;

	ipa_eth_dev_log(eth_dev, "Registering interface %s", net_dev->name);

	rc = ipa_eth_ep_register_ipa_intf(net_dev, tx_intf, rx_intf);
	if (rc) {
		ipa_eth_err("Failed to register IPA interface");
		return rc;
	}

	return 0;
}

/**
 * ipa_eth_ep_register_interface() - Set Rx and Tx properties and register the
 *                                   interface with IPA
 * @eth_dev: Offload device to register
 *
 * Register a logical interface with IPA. The API expects netdev and channels
 * allocated prior to being called.
 *
 * Return: 0 on success, negative errno code otherwise
 */
int ipa_eth_ep_register_interface(struct ipa_eth_device *eth_dev)
{
	int rc;

	ipa_eth_dev_log(eth_dev, "Registering interface with IPA driver");

	rc = ipa_eth_ep_init_interfaces(eth_dev);
	if (rc)
		return rc;

	rc = __ipa_eth_ep_register_interface(eth_dev);
	if (rc) {
		ipa_eth_dev_err(eth_dev, "Failed to register IPA interface");
		ipa_eth_ep_deinit_interfaces(eth_dev);
		return rc;
	}

	return 0;
}

/**
 * ipa_eth_ep_unregister_interface() - Unregister a previously registered
 *                                     interface
@@ -338,13 +455,79 @@ int ipa_eth_ep_unregister_interface(struct ipa_eth_device *eth_dev)
{
	int rc;

	rc = ipa_deregister_intf(eth_dev->net_dev->name);
	if (!rc)
		ipa_eth_send_msg_disconnect(eth_dev);
	ipa_eth_dev_log(eth_dev, "Unregistering interface from IPA driver");

	rc = ipa_eth_ep_deregister_ipa_intf(eth_dev->net_dev);
	if (rc) {
		ipa_eth_dev_err(eth_dev,
			"Failed to de-register one or more interfaces");
		return rc;
	}

	ipa_eth_ep_deinit_interfaces(eth_dev);

	return 0;
}

int ipa_eth_ep_register_upper_interface(
	struct ipa_eth_upper_device *upper_eth_dev)
{
	int rc;
	struct net_device *net_dev = upper_eth_dev->net_dev;
	struct ipa_eth_device *eth_dev = upper_eth_dev->eth_dev;

	ipa_eth_dev_log(eth_dev,
		"Registering upper interface %s", net_dev->name);

	if (upper_eth_dev->registered) {
		ipa_eth_dev_log(eth_dev,
			"Upper interface %s is already registered. Skipping.",
			net_dev->name);
		return 0;
	}

	rc = ipa_eth_ep_register_alt_interface(eth_dev, net_dev);
	if (rc) {
		ipa_eth_dev_err(eth_dev,
			"Failed to register upper interface %s", net_dev->name);
		return rc;
	}

	upper_eth_dev->registered = true;

	return 0;
}

int ipa_eth_ep_unregister_upper_interface(
	struct ipa_eth_upper_device *upper_eth_dev)
{
	int rc;
	struct net_device *net_dev = upper_eth_dev->net_dev;
	struct ipa_eth_device *eth_dev = upper_eth_dev->eth_dev;

	ipa_eth_dev_log(eth_dev,
		"Unegistering upper interface %s", net_dev->name);

	if (!upper_eth_dev->registered) {
		ipa_eth_dev_log(eth_dev,
			"Upper interface %s is already unregistered. Skipping.",
			net_dev->name);
		return 0;
	}

	rc = ipa_eth_ep_deregister_alt_interface(eth_dev, net_dev);
	if (rc) {
		ipa_eth_dev_err(eth_dev,
			"Failed to unregister upper interface %s",
			net_dev->name);
		return rc;
	}

	upper_eth_dev->registered = false;

	return 0;
}

/**
 * ipa_eth_ep_init_ctx - Initialize IPA endpoint context for a channel
 * @ch: Channel for which EP ctx need to be initialized
+36 −3
Original line number Diff line number Diff line
/* Copyright (c) 2019 The Linux Foundation. All rights reserved.
/* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -36,6 +36,8 @@
#define IPA_ETH_PFDEV (ipa3_ctx ? ipa3_ctx->pdev : NULL)
#define IPA_ETH_SUBSYS "ipa_eth"

#define IPA_ETH_NET_DEVICE_MAX_EVENTS (NETDEV_CHANGE_TX_QUEUE_LEN + 1)

enum ipa_eth_states {
	IPA_ETH_ST_READY,
	IPA_ETH_ST_UC_READY,
@@ -100,6 +102,26 @@ enum ipa_eth_dev_flags {
		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 linked; /* upper device is linked to real device */
	bool watching; /* registered netdevice notifier */
	bool registered; /* registered with IPA */

	struct notifier_block netdevice_nb;

	struct kref refcount;
};

struct ipa_eth_device_private {
	struct mutex upper_mutex;
	struct list_head upper_devices;
};

struct ipa_eth_bus {
	struct list_head bus_list;

@@ -173,6 +195,8 @@ int ipa_eth_offload_complete_reset(struct ipa_eth_device *eth_dev, void *data);

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_watch_upper(struct ipa_eth_device *eth_dev);
int ipa_eth_net_unwatch_upper(struct ipa_eth_device *eth_dev);

int ipa_eth_net_open_device(struct ipa_eth_device *eth_dev);
void ipa_eth_net_close_device(struct ipa_eth_device *eth_dev);
@@ -181,8 +205,15 @@ int ipa_eth_net_save_regs(struct ipa_eth_device *eth_dev);

int ipa_eth_ep_init_headers(struct ipa_eth_device *eth_dev);
int ipa_eth_ep_deinit_headers(struct ipa_eth_device *eth_dev);

int ipa_eth_ep_register_interface(struct ipa_eth_device *eth_dev);
int ipa_eth_ep_unregister_interface(struct ipa_eth_device *eth_dev);

int ipa_eth_ep_register_upper_interface(
	struct ipa_eth_upper_device *upper_eth_dev);
int ipa_eth_ep_unregister_upper_interface(
	struct ipa_eth_upper_device *upper_eth_dev);

void ipa_eth_ep_init_ctx(struct ipa_eth_channel *ch, bool vlan_mode);
void ipa_eth_ep_deinit_ctx(struct ipa_eth_channel *ch);

@@ -202,8 +233,10 @@ int ipa_eth_uc_stats_stop(struct ipa_eth_device *eth_dev);
/* ipa_eth_utils.c APIs */

const char *ipa_eth_device_event_name(enum ipa_eth_device_event event);
int ipa_eth_send_msg_connect(struct ipa_eth_device *eth_dev);
int ipa_eth_send_msg_disconnect(struct ipa_eth_device *eth_dev);
const char *ipa_eth_net_device_event_name(unsigned long event);

int ipa_eth_send_msg_connect(struct net_device *net_dev);
int ipa_eth_send_msg_disconnect(struct net_device *net_dev);

void *ipa_eth_get_ipc_logbuf(void);
void *ipa_eth_get_ipc_logbuf_dbg(void);
+323 −19
Original line number Diff line number Diff line
@@ -87,40 +87,331 @@ void ipa_eth_net_unregister_driver(struct ipa_eth_net_driver *nd)
	ipa_eth_bus_unregister_driver(nd);
}

static int ipa_eth_net_process_event(
	struct ipa_eth_device *eth_dev,
/* Event handler for netdevice events from upper interfaces */
static int ipa_eth_net_upper_event(struct notifier_block *nb,
	unsigned long event, void *ptr)
{
	bool link_changed = false;
	bool iface_changed = false;
	int rc;
	struct net_device *net_dev = netdev_notifier_info_to_dev(ptr);
	struct ipa_eth_upper_device *upper_dev =
			container_of(nb,
				struct ipa_eth_upper_device, netdevice_nb);
	struct ipa_eth_device *eth_dev = upper_dev->eth_dev;

	link_changed =
		netif_carrier_ok(eth_dev->net_dev) ?
		!test_and_set_bit(IPA_ETH_IF_ST_LOWER_UP, &eth_dev->if_state) :
		test_and_clear_bit(IPA_ETH_IF_ST_LOWER_UP, &eth_dev->if_state);
	if (net_dev != upper_dev->net_dev)
		return NOTIFY_DONE;

	ipa_eth_dev_log(eth_dev,
			"Received netdev event %s (0x%04lx) for %s",
			ipa_eth_net_device_event_name(event), event,
			net_dev->name);

	switch (event) {
	case NETDEV_UP:
		iface_changed = !test_and_set_bit(
					IPA_ETH_IF_ST_UP, &eth_dev->if_state);
		rc = ipa_eth_ep_register_upper_interface(upper_dev);
		if (rc)
			ipa_eth_dev_err(eth_dev, "Failed to register upper");
		break;
	case NETDEV_DOWN:
		iface_changed = test_and_clear_bit(
					IPA_ETH_IF_ST_UP, &eth_dev->if_state);
		rc = ipa_eth_ep_unregister_upper_interface(upper_dev);
		if (rc)
			ipa_eth_dev_err(eth_dev, "Failed to register upper");
		break;
	default:
		break;
	}

	/* We can not wait for refresh to complete because we are holding
	 * the rtnl mutex.
	return NOTIFY_DONE;
}

static void __ipa_eth_upper_release(struct kref *ref)
{
	struct ipa_eth_upper_device *upper_dev =
		container_of(ref, struct ipa_eth_upper_device, refcount);

	list_del(&upper_dev->upper_list);
	kzfree(upper_dev);
}

static inline void kref_get_upper(struct ipa_eth_upper_device *upper_dev)
{
	kref_get(&upper_dev->refcount);
}

static inline int kref_put_upper(struct ipa_eth_upper_device *upper_dev)
{
	return kref_put(&upper_dev->refcount, __ipa_eth_upper_release);
}

static int ipa_eth_net_watch_upper_device(
		struct ipa_eth_upper_device *upper_dev)
{
	int rc;
	struct ipa_eth_device *eth_dev = upper_dev->eth_dev;

	if (upper_dev->watching)
		return 0;

	ipa_eth_dev_log(eth_dev,
			"Going to watch upper device %s",
			upper_dev->net_dev->name);

	rc = register_netdevice_notifier(&upper_dev->netdevice_nb);
	if (rc) {
		ipa_eth_dev_err(eth_dev,
			"Failed to register with netdevice notifier");
		return rc;
	}

	upper_dev->watching = true;

	kref_get_upper(upper_dev);

	return 0;
}

static int ipa_eth_net_unwatch_upper_device_unsafe(
		struct ipa_eth_upper_device *upper_dev)
{
	int rc;
	struct ipa_eth_device *eth_dev = upper_dev->eth_dev;

	if (!upper_dev->watching)
		return 0;

	rc = unregister_netdevice_notifier(&upper_dev->netdevice_nb);
	if (rc) {
		ipa_eth_dev_err(eth_dev,
			"Failed to unregister with netdevice notifier");
		return rc;
	}

	ipa_eth_dev_log(eth_dev, "Stopped watching upper device %s",
			upper_dev->net_dev->name);

	upper_dev->watching = false;

	/* kref_put_upper() unlinks upper_dev from upper_devices list before
	 * freeing it, causing this function unsafe to use during linked list
	 * iteration.
	 */
	kref_put_upper(upper_dev);

	return rc;
}

static int ipa_eth_net_unwatch_unlinked(struct ipa_eth_device *eth_dev)
{
	int rc = 0;
	struct ipa_eth_device_private *dev_priv = eth_dev->ipa_priv;
	struct ipa_eth_upper_device *upper_dev = NULL;
	struct ipa_eth_upper_device *tmp = NULL;

	mutex_lock(&dev_priv->upper_mutex);

	list_for_each_entry_safe(upper_dev, tmp,
					&dev_priv->upper_devices, upper_list) {
		if (upper_dev->linked)
			continue;

		rc |= ipa_eth_net_unwatch_upper_device_unsafe(upper_dev);
	}

	mutex_unlock(&dev_priv->upper_mutex);

	return rc;
}

int ipa_eth_net_watch_upper(struct ipa_eth_device *eth_dev)
{
	int rc = 0;
	struct ipa_eth_device_private *dev_priv = eth_dev->ipa_priv;
	struct ipa_eth_upper_device *upper_dev = NULL;

	/* We cannot acquire rtnl_mutex because we need to subsequently call
	 * register_netdevice_notifier.
	 */
	mutex_lock(&dev_priv->upper_mutex);

	list_for_each_entry(upper_dev, &dev_priv->upper_devices, upper_list) {
		if (!upper_dev->linked)
			continue;

		rc = ipa_eth_net_watch_upper_device(upper_dev);
		if (rc)
			break;
	}

	if (rc) {
		list_for_each_entry_continue_reverse(upper_dev,
				&dev_priv->upper_devices, upper_list) {
			/* Since we are unwatching only linked devices, they
			 * will not be removed from the linked list, so we
			 * do not need to use safe iteration for linked list.
			 */
			if (upper_dev->linked)
				ipa_eth_net_unwatch_upper_device_unsafe(
						upper_dev);
		}
	}

	mutex_unlock(&dev_priv->upper_mutex);

	if (ipa_eth_net_unwatch_unlinked(eth_dev)) {
		ipa_eth_dev_err(eth_dev,
			"Failed to unwatch one or more unliked upper devices");
	}

	return rc;
}

int ipa_eth_net_unwatch_upper(struct ipa_eth_device *eth_dev)
{
	int rc = 0;
	struct ipa_eth_device_private *dev_priv = eth_dev->ipa_priv;
	struct ipa_eth_upper_device *upper_dev = NULL;
	struct ipa_eth_upper_device *tmp = NULL;

	mutex_lock(&dev_priv->upper_mutex);

	list_for_each_entry_safe(upper_dev, tmp,
					&dev_priv->upper_devices, upper_list)
		rc |= ipa_eth_net_unwatch_upper_device_unsafe(upper_dev);

	if (rc)
		ipa_eth_dev_err(eth_dev,
			"Failed to unwatch one or more upper devices");

	mutex_unlock(&dev_priv->upper_mutex);

	return rc;
}

static int ipa_eth_net_link_upper(struct ipa_eth_device *eth_dev,
	struct net_device *upper_net_dev)
{
	int rc = 0;
	struct ipa_eth_upper_device *upper_dev;
	struct ipa_eth_device_private *dev_priv = eth_dev->ipa_priv;

	ipa_eth_dev_log(eth_dev,
		"Linking upper interface %s", upper_net_dev->name);

	upper_dev = kzalloc(sizeof(*upper_dev), GFP_KERNEL);
	if (!upper_dev)
		return -ENOMEM;

	kref_init(&upper_dev->refcount);

	upper_dev->linked = true;
	upper_dev->eth_dev = eth_dev;
	upper_dev->net_dev = upper_net_dev;
	upper_dev->netdevice_nb.notifier_call = ipa_eth_net_upper_event;

	mutex_lock(&dev_priv->upper_mutex);
	list_add(&upper_dev->upper_list, &dev_priv->upper_devices);
	mutex_unlock(&dev_priv->upper_mutex);

	/* We cannot call register_netdevice_notifier() from here since we
	 * are already holding rtnl_mutex. Schedule a device refresh for the
	 * offload sub-system workqueue to re-scan upper list and register for
	 * notifications.
	 */
	if (link_changed || iface_changed)
	ipa_eth_device_refresh_sched(eth_dev);

	return NOTIFY_DONE;
	return rc;
}

static int ipa_eth_net_unlink_upper(struct ipa_eth_device *eth_dev,
	struct net_device *upper_net_dev)
{
	int rc = -ENODEV;
	struct ipa_eth_device_private *dev_priv = eth_dev->ipa_priv;
	struct ipa_eth_upper_device *upper_dev = NULL;

	ipa_eth_dev_log(eth_dev,
		"Unlinking upper interface %s", upper_net_dev->name);

	mutex_lock(&dev_priv->upper_mutex);

	list_for_each_entry(upper_dev, &dev_priv->upper_devices, upper_list) {
		if (upper_dev->net_dev == upper_net_dev) {
			upper_dev->linked = false;

			/* We can free upper_dev only if the refresh wq has
			 * already unregistered the netdevice notifier.
			 */
			kref_put_upper(upper_dev);

			rc = 0;
			break;
		}
	}

	mutex_unlock(&dev_priv->upper_mutex);

	ipa_eth_device_refresh_sched(eth_dev);

	return rc;
}

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

static bool ipa_eth_net_event_change(struct ipa_eth_device *eth_dev,
		unsigned long event, void *ptr)
{
	return netif_carrier_ok(eth_dev->net_dev) ?
		!test_and_set_bit(IPA_ETH_IF_ST_LOWER_UP, &eth_dev->if_state) :
		test_and_clear_bit(IPA_ETH_IF_ST_LOWER_UP, &eth_dev->if_state);

}

static bool ipa_eth_net_event_pre_change_upper(struct ipa_eth_device *eth_dev,
		unsigned long event, void *ptr)
{
	struct netdev_notifier_changeupper_info *upper_info = ptr;

	if (!upper_info->linking)
		ipa_eth_net_unlink_upper(eth_dev, upper_info->upper_dev);

	return false;
}

static bool ipa_eth_net_event_change_upper(struct ipa_eth_device *eth_dev,
		unsigned long event, void *ptr)
{
	struct netdev_notifier_changeupper_info *upper_info = ptr;

	if (upper_info->linking)
		ipa_eth_net_link_upper(eth_dev, upper_info->upper_dev);

	return false;
}

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,
	[NETDEV_PRECHANGEUPPER] = ipa_eth_net_event_pre_change_upper,
	[NETDEV_CHANGEUPPER] = ipa_eth_net_event_change_upper,
};

static int ipa_eth_net_device_event(struct notifier_block *nb,
	unsigned long event, void *ptr)
{
@@ -131,9 +422,22 @@ static int ipa_eth_net_device_event(struct notifier_block *nb,
	if (net_dev != eth_dev->net_dev)
		return NOTIFY_DONE;

	ipa_eth_dev_log(eth_dev, "Received netdev event 0x%04lx", event);
	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);
	}

	return ipa_eth_net_process_event(eth_dev, event, ptr);
	return NOTIFY_DONE;
}

int ipa_eth_net_open_device(struct ipa_eth_device *eth_dev)
+41 −9

File changed.

Preview size limit exceeded, changes collapsed.

Loading