diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 4da68ba8448fc79cd8855db299c761dafcf4bf6a..30d6e2f7686e5aa48db7069c5ec8bf00a1513565 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -431,6 +431,7 @@ config VSOCKMON config MHI_NET tristate "MHI network driver" depends on MHI_BUS + select WWAN_CORE help This is the network driver for MHI bus. It can be used with QCOM based WWAN modems (like SDX55). Say Y or M. diff --git a/drivers/net/mhi/net.c b/drivers/net/mhi/net.c index 0d8293a47a56dc7474e9529de67f3a926f0e95c4..64af1e518484ecab01b4a55edc28680384080bce 100644 --- a/drivers/net/mhi/net.c +++ b/drivers/net/mhi/net.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "mhi.h" @@ -18,6 +19,12 @@ #define MHI_NET_MAX_MTU 0xffff #define MHI_NET_DEFAULT_MTU 0x4000 +/* When set to false, the default netdev (link 0) is not created, and it's up + * to user to create the link (via wwan rtnetlink). + */ +static bool create_default_iface = true; +module_param(create_default_iface, bool, 0); + struct mhi_device_info { const char *netname; const struct mhi_net_proto *proto; @@ -295,32 +302,33 @@ static void mhi_net_rx_refill_work(struct work_struct *work) schedule_delayed_work(&mhi_netdev->rx_refill, HZ / 2); } -static struct device_type wwan_type = { - .name = "wwan", -}; - -static int mhi_net_probe(struct mhi_device *mhi_dev, - const struct mhi_device_id *id) +static int mhi_net_newlink(void *ctxt, struct net_device *ndev, u32 if_id, + struct netlink_ext_ack *extack) { - const struct mhi_device_info *info = (struct mhi_device_info *)id->driver_data; - struct device *dev = &mhi_dev->dev; + const struct mhi_device_info *info; + struct mhi_device *mhi_dev = ctxt; struct mhi_net_dev *mhi_netdev; - struct net_device *ndev; int err; - ndev = alloc_netdev(sizeof(*mhi_netdev), info->netname, - NET_NAME_PREDICTABLE, mhi_net_setup); - if (!ndev) - return -ENOMEM; + info = (struct mhi_device_info *)mhi_dev->id->driver_data; + + /* For now we only support one link (link context 0), driver must be + * reworked to break 1:1 relationship for net MBIM and to forward setup + * call to rmnet(QMAP) otherwise. + */ + if (if_id != 0) + return -EINVAL; + + if (dev_get_drvdata(&mhi_dev->dev)) + return -EBUSY; mhi_netdev = netdev_priv(ndev); - dev_set_drvdata(dev, mhi_netdev); + + dev_set_drvdata(&mhi_dev->dev, mhi_netdev); mhi_netdev->ndev = ndev; mhi_netdev->mdev = mhi_dev; mhi_netdev->skbagg_head = NULL; mhi_netdev->proto = info->proto; - SET_NETDEV_DEV(ndev, &mhi_dev->dev); - SET_NETDEV_DEVTYPE(ndev, &wwan_type); INIT_DELAYED_WORK(&mhi_netdev->rx_refill, mhi_net_rx_refill_work); u64_stats_init(&mhi_netdev->stats.rx_syncp); @@ -334,7 +342,10 @@ static int mhi_net_probe(struct mhi_device *mhi_dev, /* Number of transfer descriptors determines size of the queue */ mhi_netdev->rx_queue_sz = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); - err = register_netdev(ndev); + if (extack) + err = register_netdevice(ndev); + else + err = register_netdev(ndev); if (err) goto out_err; @@ -347,23 +358,89 @@ static int mhi_net_probe(struct mhi_device *mhi_dev, return 0; out_err_proto: - unregister_netdev(ndev); + unregister_netdevice(ndev); out_err: free_netdev(ndev); return err; } -static void mhi_net_remove(struct mhi_device *mhi_dev) +static void mhi_net_dellink(void *ctxt, struct net_device *ndev, + struct list_head *head) { - struct mhi_net_dev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + struct mhi_net_dev *mhi_netdev = netdev_priv(ndev); + struct mhi_device *mhi_dev = ctxt; - unregister_netdev(mhi_netdev->ndev); + if (head) + unregister_netdevice_queue(ndev, head); + else + unregister_netdev(ndev); - mhi_unprepare_from_transfer(mhi_netdev->mdev); + mhi_unprepare_from_transfer(mhi_dev); kfree_skb(mhi_netdev->skbagg_head); - free_netdev(mhi_netdev->ndev); + dev_set_drvdata(&mhi_dev->dev, NULL); +} + +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, + .dellink = mhi_net_dellink, +}; + +static int mhi_net_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + const struct mhi_device_info *info = (struct mhi_device_info *)id->driver_data; + struct mhi_controller *cntrl = mhi_dev->mhi_cntrl; + struct net_device *ndev; + int err; + + err = wwan_register_ops(&cntrl->mhi_dev->dev, &mhi_wwan_ops, mhi_dev); + if (err) + return err; + + if (!create_default_iface) + return 0; + + /* Create a default interface which is used as either RMNET real-dev, + * MBIM link 0 or ip link 0) + */ + ndev = alloc_netdev(sizeof(struct mhi_net_dev), info->netname, + NET_NAME_PREDICTABLE, mhi_net_setup); + if (!ndev) { + err = -ENOMEM; + goto err_unregister; + } + + SET_NETDEV_DEV(ndev, &mhi_dev->dev); + + err = mhi_net_newlink(mhi_dev, ndev, 0, NULL); + if (err) + goto err_release; + + return 0; + +err_release: + free_netdev(ndev); +err_unregister: + wwan_unregister_ops(&cntrl->mhi_dev->dev); + + return err; +} + +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_unregister_ops(&cntrl->mhi_dev->dev); + + if (create_default_iface) + mhi_net_dellink(mhi_dev, mhi_netdev->ndev, NULL); } static const struct mhi_device_info mhi_hwip0 = {