/*
* Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
*
* The probing code is heavily inspired by cdc_ether, which is:
* Copyright (C) 2003-2005 by David Brownell
* Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/mii.h>
#include <linux/rtnetlink.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/usbnet.h>
#include <linux/usb/cdc-wdm.h>
/* This driver supports wwan (3G/LTE/?) devices using a vendor
* specific management protocol called Qualcomm MSM Interface (QMI) -
* in addition to the more common AT commands over serial interface
* management
*
* QMI is wrapped in CDC, using CDC encapsulated commands on the
* control ("master") interface of a two-interface CDC Union
* resembling standard CDC ECM. The devices do not use the control
* interface for any other CDC messages. Most likely because the
* management protocol is used in place of the standard CDC
* notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE
*
* Alternatively, control and data functions can be combined in a
* single USB interface.
*
* Handling a protocol like QMI is out of the scope for any driver.
* It is exported as a character device using the cdc-wdm driver as
* a subdriver, enabling userspace applications ("modem managers") to
* handle it.
*
* These devices may alternatively/additionally be configured using AT
* commands on a serial interface
*/
/* driver specific data */
struct qmi_wwan_state {
struct usb_driver *subdriver;
atomic_t pmcount;
unsigned long flags;
struct usb_interface *control;
struct usb_interface *data;
};
enum qmi_wwan_flags {
QMI_WWAN_FLAG_RAWIP = 1 << 0,
};
static void qmi_wwan_netdev_setup(struct net_device *net)
{
struct usbnet *dev = netdev_priv(net);
struct qmi_wwan_state *info = (void *)&dev->data;
if (info->flags & QMI_WWAN_FLAG_RAWIP) {
net->header_ops = NULL; /* No header */
net->type = ARPHRD_NONE;
net->hard_header_len = 0;
net->addr_len = 0;
net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
netdev_dbg(net, "mode: raw IP\n");
} else if (!net->header_ops) { /* don't bother if already set */
ether_setup(net);
netdev_dbg(net, "mode: Ethernet\n");
}
/* recalculate buffers after changing hard_header_len */
usbnet_change_mtu(net, net->mtu);
}
static ssize_t raw_ip_show(struct device *d, struct device_attribute *attr, char *buf)
{
struct usbnet *dev = netdev_priv(to_net_dev(d));
struct qmi_wwan_state *info = (void *)&dev->data;
return sprintf(buf, "%c\n", info->flags & QMI_WWAN_FLAG_RAWIP ? 'Y' : 'N');
}
static ssize_t raw_ip_store(struct device *d, struct device_attribute *attr, const char *buf, size_t len)
{
struct usbnet *dev = netdev_priv(to_net_dev(d));
struct qmi_wwan_state *info = (void *)&dev->data;
bool enable;
int ret;
if (strtobool(buf, &enable))
return -EINVAL;
/* no change? */
if (enable == (info->flags & QMI_WWAN_FLAG_RAWIP))
return len;
if (!rtnl_trylock())
return restart_syscall();
/* we don't want to modify a running netdev */
if (netif_running(dev->net)) {
netdev_err(dev->net, "Cannot change a running device\n");
ret = -EBUSY;
goto err;
}
/* let other drivers deny the change */
ret = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev->net);
ret = notifier_to_errno(ret);
if (ret) {
netdev_err(dev->net, "Type change was refused\n");
goto err;
}
if (enable)
info->flags |= QMI_WWAN_FLAG_RAWIP;
else
info->flags &= ~QMI_WWAN_FLAG_RAWIP;
qmi_wwan_netdev_setup(dev->net);
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev->net);
ret = len;
err:
rtnl_unlock();
return ret;
}
static DEVICE_ATTR_RW(raw_ip);
static struct attribute *qmi_wwan_sysfs_attrs[] = {
&dev_attr_raw_ip.attr,
NULL,
};
static struct attribute_group qmi_wwan_sysfs_attr_group = {
.name = "qmi",
.attrs = qmi_wwan_sysfs_attrs,
};
/* default ethernet address used by the modem */
static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3};
static const<