aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/mhi/net.c18
-rw-r--r--drivers/net/mhi/proto_mbim.c5
-rw-r--r--drivers/net/wwan/iosm/iosm_ipc_imem_ops.h3
-rw-r--r--drivers/net/wwan/iosm/iosm_ipc_wwan.c31
-rw-r--r--drivers/net/wwan/wwan_core.c258
-rw-r--r--drivers/net/wwan/wwan_hwsim.c47
-rw-r--r--include/linux/wwan.h28
7 files changed, 282 insertions, 108 deletions
diff --git a/drivers/net/mhi/net.c b/drivers/net/mhi/net.c
index 6aa753387372..e60e38c1f09d 100644
--- a/drivers/net/mhi/net.c
+++ b/drivers/net/mhi/net.c
@@ -32,7 +32,7 @@ struct mhi_device_info {
static int mhi_ndo_open(struct net_device *ndev)
{
- struct mhi_net_dev *mhi_netdev = netdev_priv(ndev);
+ struct mhi_net_dev *mhi_netdev = wwan_netdev_drvpriv(ndev);
/* Feed the rx buffer pool */
schedule_delayed_work(&mhi_netdev->rx_refill, 0);
@@ -47,7 +47,7 @@ static int mhi_ndo_open(struct net_device *ndev)
static int mhi_ndo_stop(struct net_device *ndev)
{
- struct mhi_net_dev *mhi_netdev = netdev_priv(ndev);
+ struct mhi_net_dev *mhi_netdev = wwan_netdev_drvpriv(ndev);
netif_stop_queue(ndev);
netif_carrier_off(ndev);
@@ -58,7 +58,7 @@ static int mhi_ndo_stop(struct net_device *ndev)
static netdev_tx_t mhi_ndo_xmit(struct sk_buff *skb, struct net_device *ndev)
{
- struct mhi_net_dev *mhi_netdev = netdev_priv(ndev);
+ struct mhi_net_dev *mhi_netdev = wwan_netdev_drvpriv(ndev);
const struct mhi_net_proto *proto = mhi_netdev->proto;
struct mhi_device *mdev = mhi_netdev->mdev;
int err;
@@ -93,7 +93,7 @@ exit_drop:
static void mhi_ndo_get_stats64(struct net_device *ndev,
struct rtnl_link_stats64 *stats)
{
- struct mhi_net_dev *mhi_netdev = netdev_priv(ndev);
+ struct mhi_net_dev *mhi_netdev = wwan_netdev_drvpriv(ndev);
unsigned int start;
do {
@@ -322,7 +322,7 @@ static int mhi_net_newlink(void *ctxt, struct net_device *ndev, u32 if_id,
if (dev_get_drvdata(&mhi_dev->dev))
return -EBUSY;
- mhi_netdev = netdev_priv(ndev);
+ mhi_netdev = wwan_netdev_drvpriv(ndev);
dev_set_drvdata(&mhi_dev->dev, mhi_netdev);
mhi_netdev->ndev = ndev;
@@ -367,7 +367,7 @@ out_err:
static void mhi_net_dellink(void *ctxt, struct net_device *ndev,
struct list_head *head)
{
- struct mhi_net_dev *mhi_netdev = netdev_priv(ndev);
+ struct mhi_net_dev *mhi_netdev = wwan_netdev_drvpriv(ndev);
struct mhi_device *mhi_dev = ctxt;
if (head)
@@ -383,7 +383,6 @@ static void mhi_net_dellink(void *ctxt, struct net_device *ndev,
}
static const struct wwan_ops mhi_wwan_ops = {
- .owner = THIS_MODULE,
.priv_size = sizeof(struct mhi_net_dev),
.setup = mhi_net_setup,
.newlink = mhi_net_newlink,
@@ -398,7 +397,8 @@ static int mhi_net_probe(struct mhi_device *mhi_dev,
struct net_device *ndev;
int err;
- err = wwan_register_ops(&cntrl->mhi_dev->dev, &mhi_wwan_ops, mhi_dev);
+ err = wwan_register_ops(&cntrl->mhi_dev->dev, &mhi_wwan_ops, mhi_dev,
+ WWAN_NO_DEFAULT_LINK);
if (err)
return err;
@@ -436,7 +436,7 @@ static void mhi_net_remove(struct mhi_device *mhi_dev)
struct mhi_net_dev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev);
struct mhi_controller *cntrl = mhi_dev->mhi_cntrl;
- /* rtnetlink takes care of removing remaining links */
+ /* WWAN core takes care of removing remaining links */
wwan_unregister_ops(&cntrl->mhi_dev->dev);
if (create_default_iface)
diff --git a/drivers/net/mhi/proto_mbim.c b/drivers/net/mhi/proto_mbim.c
index fc72b3f6ec9e..bf1ad863237d 100644
--- a/drivers/net/mhi/proto_mbim.c
+++ b/drivers/net/mhi/proto_mbim.c
@@ -16,6 +16,7 @@
#include <linux/ip.h>
#include <linux/mii.h>
#include <linux/netdevice.h>
+#include <linux/wwan.h>
#include <linux/skbuff.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
@@ -56,7 +57,7 @@ static void __mbim_errors_inc(struct mhi_net_dev *dev)
static int mbim_rx_verify_nth16(struct sk_buff *skb)
{
- struct mhi_net_dev *dev = netdev_priv(skb->dev);
+ struct mhi_net_dev *dev = wwan_netdev_drvpriv(skb->dev);
struct mbim_context *ctx = dev->proto_data;
struct usb_cdc_ncm_nth16 *nth16;
int len;
@@ -102,7 +103,7 @@ static int mbim_rx_verify_nth16(struct sk_buff *skb)
static int mbim_rx_verify_ndp16(struct sk_buff *skb, struct usb_cdc_ncm_ndp16 *ndp16)
{
- struct mhi_net_dev *dev = netdev_priv(skb->dev);
+ struct mhi_net_dev *dev = wwan_netdev_drvpriv(skb->dev);
int ret;
if (le16_to_cpu(ndp16->wLength) < USB_CDC_NCM_NDP16_LENGTH_MIN) {
diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h
index 84087cf33329..fd356dafbdd6 100644
--- a/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h
+++ b/drivers/net/wwan/iosm/iosm_ipc_imem_ops.h
@@ -30,6 +30,9 @@
#define IP_MUX_SESSION_START 1
#define IP_MUX_SESSION_END 8
+/* Default IP MUX channel */
+#define IP_MUX_SESSION_DEFAULT 1
+
/**
* ipc_imem_sys_port_open - Open a port link to CP.
* @ipc_imem: Imem instance.
diff --git a/drivers/net/wwan/iosm/iosm_ipc_wwan.c b/drivers/net/wwan/iosm/iosm_ipc_wwan.c
index 1711b79fc616..c999c64001f4 100644
--- a/drivers/net/wwan/iosm/iosm_ipc_wwan.c
+++ b/drivers/net/wwan/iosm/iosm_ipc_wwan.c
@@ -20,7 +20,7 @@
#define IOSM_IF_ID_PAYLOAD 2
/**
- * struct iosm_netdev_priv - netdev private data
+ * struct iosm_netdev_priv - netdev WWAN driver specific private data
* @ipc_wwan: Pointer to iosm_wwan struct
* @netdev: Pointer to network interface device structure
* @if_id: Interface id for device.
@@ -51,7 +51,7 @@ struct iosm_wwan {
/* Bring-up the wwan net link */
static int ipc_wwan_link_open(struct net_device *netdev)
{
- struct iosm_netdev_priv *priv = netdev_priv(netdev);
+ struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
struct iosm_wwan *ipc_wwan = priv->ipc_wwan;
int if_id = priv->if_id;
int ret;
@@ -88,7 +88,7 @@ out:
/* Bring-down the wwan net link */
static int ipc_wwan_link_stop(struct net_device *netdev)
{
- struct iosm_netdev_priv *priv = netdev_priv(netdev);
+ struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
netif_stop_queue(netdev);
@@ -105,7 +105,7 @@ static int ipc_wwan_link_stop(struct net_device *netdev)
static int ipc_wwan_link_transmit(struct sk_buff *skb,
struct net_device *netdev)
{
- struct iosm_netdev_priv *priv = netdev_priv(netdev);
+ struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
struct iosm_wwan *ipc_wwan = priv->ipc_wwan;
int if_id = priv->if_id;
int ret;
@@ -178,7 +178,7 @@ static int ipc_wwan_newlink(void *ctxt, struct net_device *dev,
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
return -EINVAL;
- priv = netdev_priv(dev);
+ priv = wwan_netdev_drvpriv(dev);
priv->if_id = if_id;
priv->netdev = dev;
priv->ipc_wwan = ipc_wwan;
@@ -208,8 +208,8 @@ out_unlock:
static void ipc_wwan_dellink(void *ctxt, struct net_device *dev,
struct list_head *head)
{
+ struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(dev);
struct iosm_wwan *ipc_wwan = ctxt;
- struct iosm_netdev_priv *priv = netdev_priv(dev);
int if_id = priv->if_id;
if (WARN_ON(if_id < IP_MUX_SESSION_START ||
@@ -317,7 +317,9 @@ struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev)
ipc_wwan->dev = dev;
ipc_wwan->ipc_imem = ipc_imem;
- if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan)) {
+ /* WWAN core will create a netdev for the default IP MUX channel */
+ if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan,
+ IP_MUX_SESSION_DEFAULT)) {
kfree(ipc_wwan);
return NULL;
}
@@ -329,22 +331,9 @@ struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev)
void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan)
{
- int if_id;
-
+ /* This call will remove all child netdev(s) */
wwan_unregister_ops(ipc_wwan->dev);
- for (if_id = 0; if_id < ARRAY_SIZE(ipc_wwan->sub_netlist); if_id++) {
- struct iosm_netdev_priv *priv;
-
- priv = rcu_access_pointer(ipc_wwan->sub_netlist[if_id]);
- if (!priv)
- continue;
-
- rtnl_lock();
- ipc_wwan_dellink(ipc_wwan, priv->netdev, NULL);
- rtnl_unlock();
- }
-
mutex_destroy(&ipc_wwan->if_mutex);
kfree(ipc_wwan);
diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
index 165afec1dbd1..3e16c318e705 100644
--- a/drivers/net/wwan/wwan_core.c
+++ b/drivers/net/wwan/wwan_core.c
@@ -789,77 +789,6 @@ static const struct file_operations wwan_port_fops = {
.llseek = noop_llseek,
};
-/**
- * wwan_register_ops - register WWAN device ops
- * @parent: Device to use as parent and shared by all WWAN ports and
- * created netdevs
- * @ops: operations to register
- * @ctxt: context to pass to operations
- *
- * Returns: 0 on success, a negative error code on failure
- */
-int wwan_register_ops(struct device *parent, const struct wwan_ops *ops,
- void *ctxt)
-{
- struct wwan_device *wwandev;
-
- if (WARN_ON(!parent || !ops))
- return -EINVAL;
-
- wwandev = wwan_create_dev(parent);
- if (!wwandev)
- return -ENOMEM;
-
- if (WARN_ON(wwandev->ops)) {
- wwan_remove_dev(wwandev);
- return -EBUSY;
- }
-
- if (!try_module_get(ops->owner)) {
- wwan_remove_dev(wwandev);
- return -ENODEV;
- }
-
- wwandev->ops = ops;
- wwandev->ops_ctxt = ctxt;
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(wwan_register_ops);
-
-/**
- * wwan_unregister_ops - remove WWAN device ops
- * @parent: Device to use as parent and shared by all WWAN ports and
- * created netdevs
- */
-void wwan_unregister_ops(struct device *parent)
-{
- struct wwan_device *wwandev = wwan_dev_get_by_parent(parent);
- bool has_ops;
-
- if (WARN_ON(IS_ERR(wwandev)))
- return;
-
- has_ops = wwandev->ops;
-
- /* put the reference obtained by wwan_dev_get_by_parent(),
- * we should still have one (that the owner is giving back
- * now) due to the ops being assigned, check that below
- * and return if not.
- */
- put_device(&wwandev->dev);
-
- if (WARN_ON(!has_ops))
- return;
-
- module_put(wwandev->ops->owner);
-
- wwandev->ops = NULL;
- wwandev->ops_ctxt = NULL;
- wwan_remove_dev(wwandev);
-}
-EXPORT_SYMBOL_GPL(wwan_unregister_ops);
-
static int wwan_rtnl_validate(struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
@@ -886,6 +815,7 @@ static struct net_device *wwan_rtnl_alloc(struct nlattr *tb[],
const char *devname = nla_data(tb[IFLA_PARENT_DEV_NAME]);
struct wwan_device *wwandev = wwan_dev_get_by_name(devname);
struct net_device *dev;
+ unsigned int priv_size;
if (IS_ERR(wwandev))
return ERR_CAST(wwandev);
@@ -896,7 +826,8 @@ static struct net_device *wwan_rtnl_alloc(struct nlattr *tb[],
goto out;
}
- dev = alloc_netdev_mqs(wwandev->ops->priv_size, ifname, name_assign_type,
+ priv_size = sizeof(struct wwan_netdev_priv) + wwandev->ops->priv_size;
+ dev = alloc_netdev_mqs(priv_size, ifname, name_assign_type,
wwandev->ops->setup, num_tx_queues, num_rx_queues);
if (dev) {
@@ -916,6 +847,7 @@ static int wwan_rtnl_newlink(struct net *src_net, struct net_device *dev,
{
struct wwan_device *wwandev = wwan_dev_get_by_parent(dev->dev.parent);
u32 link_id = nla_get_u32(data[IFLA_WWAN_LINK_ID]);
+ struct wwan_netdev_priv *priv = netdev_priv(dev);
int ret;
if (IS_ERR(wwandev))
@@ -927,6 +859,7 @@ static int wwan_rtnl_newlink(struct net *src_net, struct net_device *dev,
goto out;
}
+ priv->link_id = link_id;
if (wwandev->ops->newlink)
ret = wwandev->ops->newlink(wwandev->ops_ctxt, dev,
link_id, extack);
@@ -953,13 +886,34 @@ static void wwan_rtnl_dellink(struct net_device *dev, struct list_head *head)
if (wwandev->ops->dellink)
wwandev->ops->dellink(wwandev->ops_ctxt, dev, head);
else
- unregister_netdevice(dev);
+ unregister_netdevice_queue(dev, head);
out:
/* release the reference */
put_device(&wwandev->dev);
}
+static size_t wwan_rtnl_get_size(const struct net_device *dev)
+{
+ return
+ nla_total_size(4) + /* IFLA_WWAN_LINK_ID */
+ 0;
+}
+
+static int wwan_rtnl_fill_info(struct sk_buff *skb,
+ const struct net_device *dev)
+{
+ struct wwan_netdev_priv *priv = netdev_priv(dev);
+
+ if (nla_put_u32(skb, IFLA_WWAN_LINK_ID, priv->link_id))
+ goto nla_put_failure;
+
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
static const struct nla_policy wwan_rtnl_policy[IFLA_WWAN_MAX + 1] = {
[IFLA_WWAN_LINK_ID] = { .type = NLA_U32 },
};
@@ -971,9 +925,167 @@ static struct rtnl_link_ops wwan_rtnl_link_ops __read_mostly = {
.validate = wwan_rtnl_validate,
.newlink = wwan_rtnl_newlink,
.dellink = wwan_rtnl_dellink,
+ .get_size = wwan_rtnl_get_size,
+ .fill_info = wwan_rtnl_fill_info,
.policy = wwan_rtnl_policy,
};
+static void wwan_create_default_link(struct wwan_device *wwandev,
+ u32 def_link_id)
+{
+ struct nlattr *tb[IFLA_MAX + 1], *linkinfo[IFLA_INFO_MAX + 1];
+ struct nlattr *data[IFLA_WWAN_MAX + 1];
+ struct net_device *dev;
+ struct nlmsghdr *nlh;
+ struct sk_buff *msg;
+
+ /* Forge attributes required to create a WWAN netdev. We first
+ * build a netlink message and then parse it. This looks
+ * odd, but such approach is less error prone.
+ */
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (WARN_ON(!msg))
+ return;
+ nlh = nlmsg_put(msg, 0, 0, RTM_NEWLINK, 0, 0);
+ if (WARN_ON(!nlh))
+ goto free_attrs;
+
+ if (nla_put_string(msg, IFLA_PARENT_DEV_NAME, dev_name(&wwandev->dev)))
+ goto free_attrs;
+ tb[IFLA_LINKINFO] = nla_nest_start(msg, IFLA_LINKINFO);
+ if (!tb[IFLA_LINKINFO])
+ goto free_attrs;
+ linkinfo[IFLA_INFO_DATA] = nla_nest_start(msg, IFLA_INFO_DATA);
+ if (!linkinfo[IFLA_INFO_DATA])
+ goto free_attrs;
+ if (nla_put_u32(msg, IFLA_WWAN_LINK_ID, def_link_id))
+ goto free_attrs;
+ nla_nest_end(msg, linkinfo[IFLA_INFO_DATA]);
+ nla_nest_end(msg, tb[IFLA_LINKINFO]);
+
+ nlmsg_end(msg, nlh);
+
+ /* The next three parsing calls can not fail */
+ nlmsg_parse_deprecated(nlh, 0, tb, IFLA_MAX, NULL, NULL);
+ nla_parse_nested_deprecated(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO],
+ NULL, NULL);
+ nla_parse_nested_deprecated(data, IFLA_WWAN_MAX,
+ linkinfo[IFLA_INFO_DATA], NULL, NULL);
+
+ rtnl_lock();
+
+ dev = rtnl_create_link(&init_net, "wwan%d", NET_NAME_ENUM,
+ &wwan_rtnl_link_ops, tb, NULL);
+ if (WARN_ON(IS_ERR(dev)))
+ goto unlock;
+
+ if (WARN_ON(wwan_rtnl_newlink(&init_net, dev, tb, data, NULL))) {
+ free_netdev(dev);
+ goto unlock;
+ }
+
+unlock:
+ rtnl_unlock();
+
+free_attrs:
+ nlmsg_free(msg);
+}
+
+/**
+ * wwan_register_ops - register WWAN device ops
+ * @parent: Device to use as parent and shared by all WWAN ports and
+ * created netdevs
+ * @ops: operations to register
+ * @ctxt: context to pass to operations
+ * @def_link_id: id of the default link that will be automatically created by
+ * the WWAN core for the WWAN device. The default link will not be created
+ * if the passed value is WWAN_NO_DEFAULT_LINK.
+ *
+ * Returns: 0 on success, a negative error code on failure
+ */
+int wwan_register_ops(struct device *parent, const struct wwan_ops *ops,
+ void *ctxt, u32 def_link_id)
+{
+ struct wwan_device *wwandev;
+
+ if (WARN_ON(!parent || !ops || !ops->setup))
+ return -EINVAL;
+
+ wwandev = wwan_create_dev(parent);
+ if (!wwandev)
+ return -ENOMEM;
+
+ if (WARN_ON(wwandev->ops)) {
+ wwan_remove_dev(wwandev);
+ return -EBUSY;
+ }
+
+ wwandev->ops = ops;
+ wwandev->ops_ctxt = ctxt;
+
+ /* NB: we do not abort ops registration in case of default link
+ * creation failure. Link ops is the management interface, while the
+ * default link creation is a service option. And we should not prevent
+ * a user from manually creating a link latter if service option failed
+ * now.
+ */
+ if (def_link_id != WWAN_NO_DEFAULT_LINK)
+ wwan_create_default_link(wwandev, def_link_id);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wwan_register_ops);
+
+/* Enqueue child netdev deletion */
+static int wwan_child_dellink(struct device *dev, void *data)
+{
+ struct list_head *kill_list = data;
+
+ if (dev->type == &wwan_type)
+ wwan_rtnl_dellink(to_net_dev(dev), kill_list);
+
+ return 0;
+}
+
+/**
+ * wwan_unregister_ops - remove WWAN device ops
+ * @parent: Device to use as parent and shared by all WWAN ports and
+ * created netdevs
+ */
+void wwan_unregister_ops(struct device *parent)
+{
+ struct wwan_device *wwandev = wwan_dev_get_by_parent(parent);
+ LIST_HEAD(kill_list);
+
+ if (WARN_ON(IS_ERR(wwandev)))
+ return;
+ if (WARN_ON(!wwandev->ops)) {
+ put_device(&wwandev->dev);
+ return;
+ }
+
+ /* put the reference obtained by wwan_dev_get_by_parent(),
+ * we should still have one (that the owner is giving back
+ * now) due to the ops being assigned.
+ */
+ put_device(&wwandev->dev);
+
+ rtnl_lock(); /* Prevent concurent netdev(s) creation/destroying */
+
+ /* Remove all child netdev(s), using batch removing */
+ device_for_each_child(&wwandev->dev, &kill_list,
+ wwan_child_dellink);
+ unregister_netdevice_many(&kill_list);
+
+ wwandev->ops = NULL; /* Finally remove ops */
+
+ rtnl_unlock();
+
+ wwandev->ops_ctxt = NULL;
+ wwan_remove_dev(wwandev);
+}
+EXPORT_SYMBOL_GPL(wwan_unregister_ops);
+
static int __init wwan_init(void)
{
int err;
diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
index 472cae544a2b..5b62cf3b3c42 100644
--- a/drivers/net/wwan/wwan_hwsim.c
+++ b/drivers/net/wwan/wwan_hwsim.c
@@ -14,10 +14,13 @@
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/skbuff.h>
+#include <linux/netdevice.h>
#include <linux/wwan.h>
#include <linux/debugfs.h>
#include <linux/workqueue.h>
+#include <net/arp.h>
+
static int wwan_hwsim_devsnum = 2;
module_param_named(devices, wwan_hwsim_devsnum, int, 0444);
MODULE_PARM_DESC(devices, "Number of simulated devices");
@@ -64,6 +67,37 @@ static const struct file_operations wwan_hwsim_debugfs_devdestroy_fops;
static void wwan_hwsim_port_del_work(struct work_struct *work);
static void wwan_hwsim_dev_del_work(struct work_struct *work);
+static netdev_tx_t wwan_hwsim_netdev_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += skb->len;
+ consume_skb(skb);
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops wwan_hwsim_netdev_ops = {
+ .ndo_start_xmit = wwan_hwsim_netdev_xmit,
+};
+
+static void wwan_hwsim_netdev_setup(struct net_device *ndev)
+{
+ ndev->netdev_ops = &wwan_hwsim_netdev_ops;
+ ndev->needs_free_netdev = true;
+
+ ndev->mtu = ETH_DATA_LEN;
+ ndev->min_mtu = ETH_MIN_MTU;
+ ndev->max_mtu = ETH_MAX_MTU;
+
+ ndev->type = ARPHRD_NONE;
+ ndev->flags = IFF_POINTOPOINT | IFF_NOARP;
+}
+
+static const struct wwan_ops wwan_hwsim_wwan_rtnl_ops = {
+ .priv_size = 0, /* No private data */
+ .setup = wwan_hwsim_netdev_setup,
+};
+
static int wwan_hwsim_port_start(struct wwan_port *wport)
{
struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport);
@@ -254,6 +288,10 @@ static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void)
INIT_WORK(&dev->del_work, wwan_hwsim_dev_del_work);
+ err = wwan_register_ops(&dev->dev, &wwan_hwsim_wwan_rtnl_ops, dev, 1);
+ if (err)
+ goto err_unreg_dev;
+
dev->debugfs_topdir = debugfs_create_dir(dev_name(&dev->dev),
wwan_hwsim_debugfs_topdir);
debugfs_create_file("destroy", 0200, dev->debugfs_topdir, dev,
@@ -265,6 +303,12 @@ static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void)
return dev;
+err_unreg_dev:
+ device_unregister(&dev->dev);
+ /* Memory will be freed in the device release callback */
+
+ return ERR_PTR(err);
+
err_free_dev:
kfree(dev);
@@ -290,6 +334,9 @@ static void wwan_hwsim_dev_del(struct wwan_hwsim_dev *dev)
debugfs_remove(dev->debugfs_topdir);
+ /* This will remove all child netdev(s) */
+ wwan_unregister_ops(&dev->dev);
+
/* Make sure that there is no pending deletion work */
if (current_work() != &dev->del_work)
cancel_work_sync(&dev->del_work);
diff --git a/include/linux/wwan.h b/include/linux/wwan.h
index 34222230360c..9fac819f92e3 100644
--- a/include/linux/wwan.h
+++ b/include/linux/wwan.h
@@ -9,6 +9,7 @@
#include <linux/poll.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
+#include <linux/netdevice.h>
/**
* enum wwan_port_type - WWAN port types
@@ -127,15 +128,36 @@ void wwan_port_txon(struct wwan_port *port);
void *wwan_port_get_drvdata(struct wwan_port *port);
/**
+ * struct wwan_netdev_priv - WWAN core network device private data
+ * @link_id: WWAN device data link id
+ * @drv_priv: driver private data area, size is determined in &wwan_ops
+ */
+struct wwan_netdev_priv {
+ u32 link_id;
+
+ /* must be last */
+ u8 drv_priv[] __aligned(sizeof(void *));
+};
+
+static inline void *wwan_netdev_drvpriv(struct net_device *dev)
+{
+ return ((struct wwan_netdev_priv *)netdev_priv(dev))->drv_priv;
+}
+
+/*
+ * Used to indicate that the WWAN core should not create a default network
+ * link.
+ */
+#define WWAN_NO_DEFAULT_LINK U32_MAX
+
+/**
* struct wwan_ops - WWAN device ops
- * @owner: module owner of the WWAN ops
* @priv_size: size of private netdev data area
* @setup: set up a new netdev
* @newlink: register the new netdev
* @dellink: remove the given netdev
*/
struct wwan_ops {
- struct module *owner;
unsigned int priv_size;
void (*setup)(struct net_device *dev);
int (*newlink)(void *ctxt, struct net_device *dev,
@@ -145,7 +167,7 @@ struct wwan_ops {
};
int wwan_register_ops(struct device *parent, const struct wwan_ops *ops,
- void *ctxt);
+ void *ctxt, u32 def_link_id);
void wwan_unregister_ops(struct device *parent);