summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorHarout Hedeshian <harouth@codeaurora.org>2014-03-25 13:50:57 -0600
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:05:12 -0700
commitbfbf6cb3adf645a15609901d84acb9709482f895 (patch)
tree1465d3aaff9bdb124c270e087024a855c580d4c6 /net
parent2d06acfdbd3dc6d6b2bc14eced4a46ecf80e0cbb (diff)
net: rmnet_data: Clear VNDs upon physical device unregistration
Clear out VNDs which have their egress device pointing to an interface which is trying to unregister from the network stack. Required to prevent systems hangs on unexpected shutdown/reboot of the device. CRs-Fixed: 638324 Change-Id: I406270fee9feb1f9673b3391ce51c11e8e6c9d81 Signed-off-by: Harout Hedeshian <harouth@codeaurora.org>
Diffstat (limited to 'net')
-rw-r--r--net/rmnet_data/rmnet_data_config.c58
-rw-r--r--net/rmnet_data/rmnet_data_vnd.c40
-rw-r--r--net/rmnet_data/rmnet_data_vnd.h1
3 files changed, 93 insertions, 6 deletions
diff --git a/net/rmnet_data/rmnet_data_config.c b/net/rmnet_data/rmnet_data_config.c
index 2eca68b4b665..907db1c801b5 100644
--- a/net/rmnet_data/rmnet_data_config.c
+++ b/net/rmnet_data/rmnet_data_config.c
@@ -45,6 +45,11 @@ static struct notifier_block rmnet_dev_notifier = {
#define RMNET_NL_MSG_SIZE(Y) (sizeof(((struct rmnet_nl_msg_s *)0)->Y))
+struct rmnet_free_vnd_work {
+ struct work_struct work;
+ int vnd_id;
+};
+
/* ***************** Init and Cleanup *************************************** */
#ifdef RMNET_KERNEL_PRE_3_8
@@ -966,6 +971,36 @@ int rmnet_free_vnd(int id)
return rmnet_vnd_free_dev(id);
}
+static void _rmnet_free_vnd_later(struct work_struct *work)
+{
+ struct rmnet_free_vnd_work *fwork;
+ fwork = (struct rmnet_free_vnd_work *) work;
+ rmnet_free_vnd(fwork->vnd_id);
+ kfree(work);
+}
+
+/**
+ * rmnet_free_vnd_later() - Schedule a work item to free virtual network device
+ * @id: RmNet virtual device node id
+ *
+ * Schedule the VND to be freed at a later time. We need to do this if the
+ * rtnl lock is already held as to prevent a deadlock.
+ */
+static void rmnet_free_vnd_later(int id)
+{
+ struct rmnet_free_vnd_work *work;
+ LOGL("(%d);", id);
+ work = (struct rmnet_free_vnd_work *)
+ kmalloc(sizeof(struct rmnet_free_vnd_work), GFP_KERNEL);
+ if (!work) {
+ LOGH("Failed to queue removal of VND:%d", id);
+ return;
+ }
+ INIT_WORK((struct work_struct *)work, _rmnet_free_vnd_later);
+ work->vnd_id = id;
+ schedule_work((struct work_struct *)work);
+}
+
/**
* rmnet_force_unassociate_device() - Force a device to unassociate
* @dev: Device to unassociate
@@ -976,6 +1011,8 @@ int rmnet_free_vnd(int id)
static void rmnet_force_unassociate_device(struct net_device *dev)
{
int i;
+ struct net_device *vndev;
+ struct rmnet_logical_ep_conf_s *cfg;
if (!dev)
BUG();
@@ -985,6 +1022,27 @@ static void rmnet_force_unassociate_device(struct net_device *dev)
return;
}
+ /* Check the VNDs for offending mappings */
+ for (i = 0; i < RMNET_DATA_MAX_VND; i++) {
+ vndev = rmnet_vnd_get_by_id(i);
+ if (!vndev) {
+ LOGL("VND %d not in use; skipping", i);
+ continue;
+ }
+ cfg = rmnet_vnd_get_le_config(vndev);
+ if (!cfg) {
+ LOGH("Got NULL config from VND %d", i);
+ BUG();
+ continue;
+ }
+ if (cfg->refcount && (cfg->egress_dev == dev)) {
+ rmnet_unset_logical_endpoint_config(vndev,
+ RMNET_LOCAL_LOGICAL_ENDPOINT);
+ rmnet_free_vnd_later(i);
+ }
+ }
+
+ /* Clear on the mappings on the phys ep */
rmnet_unset_logical_endpoint_config(dev, RMNET_LOCAL_LOGICAL_ENDPOINT);
for (i = 0; i < RMNET_DATA_MAX_LOGICAL_EP; i++)
rmnet_unset_logical_endpoint_config(dev, i);
diff --git a/net/rmnet_data/rmnet_data_vnd.c b/net/rmnet_data/rmnet_data_vnd.c
index be5a900923d9..2c662af13065 100644
--- a/net/rmnet_data/rmnet_data_vnd.c
+++ b/net/rmnet_data/rmnet_data_vnd.c
@@ -611,22 +611,32 @@ int rmnet_vnd_create_dev(int id, struct net_device **new_device,
int rmnet_vnd_free_dev(int id)
{
struct rmnet_logical_ep_conf_s *epconfig_l;
+ struct net_device *dev;
+ rtnl_lock();
if ((id < 0) || (id >= RMNET_DATA_MAX_VND) || !rmnet_devices[id]) {
+ rtnl_unlock();
LOGM("Invalid id [%d]", id);
return RMNET_CONFIG_NO_SUCH_DEVICE;
}
epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]);
- if (epconfig_l && epconfig_l->refcount)
- return RMNET_CONFIG_DEVICE_IN_USE;
+ if (epconfig_l && epconfig_l->refcount) {
+ rtnl_unlock();
+ return RMNET_CONFIG_DEVICE_IN_USE;
+ }
- unregister_netdev(rmnet_devices[id]);
- free_netdev(rmnet_devices[id]);
- rtnl_lock();
+ dev = rmnet_devices[id];
rmnet_devices[id] = 0;
rtnl_unlock();
- return 0;
+
+ if (dev) {
+ unregister_netdev(dev);
+ free_netdev(dev);
+ return 0;
+ } else {
+ return RMNET_CONFIG_NO_SUCH_DEVICE;
+ }
}
/**
@@ -1024,3 +1034,21 @@ fcdone:
return error;
}
+
+/**
+ * rmnet_vnd_get_by_id() - Get VND by array index ID
+ * @id: Virtual network deice id [0:RMNET_DATA_MAX_VND]
+ *
+ * Return:
+ * - 0 if no device or ID out of range
+ * - otherwise return pointer to VND net_device struct
+ */
+struct net_device *rmnet_vnd_get_by_id(int id)
+{
+ if (id < 0 || id >= RMNET_DATA_MAX_VND) {
+ pr_err("Bug; VND ID out of bounds");
+ BUG();
+ return 0;
+ }
+ return rmnet_devices[id];
+}
diff --git a/net/rmnet_data/rmnet_data_vnd.h b/net/rmnet_data/rmnet_data_vnd.h
index a124b4567fcc..cb57f571be13 100644
--- a/net/rmnet_data/rmnet_data_vnd.h
+++ b/net/rmnet_data/rmnet_data_vnd.h
@@ -36,5 +36,6 @@ int rmnet_vnd_add_tc_flow(uint32_t id, uint32_t map_flow, uint32_t tc_flow);
int rmnet_vnd_del_tc_flow(uint32_t id, uint32_t map_flow, uint32_t tc_flow);
int rmnet_vnd_init(void);
void rmnet_vnd_exit(void);
+struct net_device *rmnet_vnd_get_by_id(int id);
#endif /* _RMNET_DATA_VND_H_ */