M7350/kernel/drivers/usb/gadget/function/u_qc_ether.c

455 lines
12 KiB
C
Raw Permalink Normal View History

2024-09-09 08:52:07 +00:00
/*
* u_qc_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack
*
* Copyright (C) 2003-2005,2008 David Brownell
* Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
* Copyright (C) 2008 Nokia Corporation
2024-09-09 08:57:42 +00:00
* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
2024-09-09 08:52:07 +00:00
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* #define VERBOSE_DEBUG */
#include <linux/kernel.h>
#include <linux/gfp.h>
#include <linux/device.h>
#include <linux/ctype.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include "u_ether.h"
/*
* This component encapsulates the Ethernet link glue needed to provide
* one (!) network link through the USB gadget stack, normally "usb0".
*
* The control and data models are handled by the function driver which
* connects to this code; such as CDC Ethernet (ECM or EEM),
* "CDC Subset", or RNDIS. That includes all descriptor and endpoint
* management.
*
* Link level addressing is handled by this component using module
* parameters; if no such parameters are provided, random link level
* addresses are used. Each end of the link uses one address. The
* host end address is exported in various ways, and is often recorded
* in configuration databases.
*
* The driver which assembles each configuration using such a link is
* responsible for ensuring that each configuration includes at most one
* instance of is network link. (The network layer provides ways for
* this single "physical" link to be used by multiple virtual links.)
*
* This utilities is based on Ethernet-over-USB link layer utilities and
* contains MSM specific implementation.
*/
#define UETH__VERSION "29-May-2008"
struct eth_qc_dev {
/* lock is held while accessing port_usb
* or updating its backlink port_usb->ioport
*/
spinlock_t lock;
struct qc_gether *port_usb;
struct net_device *net;
struct usb_gadget *gadget;
unsigned header_len;
bool zlp;
u8 host_mac[ETH_ALEN];
};
/*-------------------------------------------------------------------------*/
#undef DBG
#undef VDBG
#undef ERROR
#undef INFO
#define xprintk(d, level, fmt, args...) \
printk(level "%s: " fmt , (d)->net->name , ## args)
#ifdef DEBUG
#undef DEBUG
#define DBG(dev, fmt, args...) \
xprintk(dev , KERN_DEBUG , fmt , ## args)
#else
#define DBG(dev, fmt, args...) \
do { } while (0)
#endif /* DEBUG */
#ifdef VERBOSE_DEBUG
#define VDBG DBG
#else
#define VDBG(dev, fmt, args...) \
do { } while (0)
#endif /* DEBUG */
#define ERROR(dev, fmt, args...) \
xprintk(dev , KERN_ERR , fmt , ## args)
#define INFO(dev, fmt, args...) \
xprintk(dev , KERN_INFO , fmt , ## args)
/*-------------------------------------------------------------------------*/
/* NETWORK DRIVER HOOKUP (to the layer above this driver) */
static int ueth_qc_change_mtu(struct net_device *net, int new_mtu)
{
struct eth_qc_dev *dev = netdev_priv(net);
unsigned long flags;
int status = 0;
/* don't change MTU on "live" link (peer won't know) */
spin_lock_irqsave(&dev->lock, flags);
if (dev->port_usb)
status = -EBUSY;
else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN)
status = -ERANGE;
else
net->mtu = new_mtu;
spin_unlock_irqrestore(&dev->lock, flags);
return status;
}
static void eth_qc_get_drvinfo(struct net_device *net,
struct ethtool_drvinfo *p)
{
struct eth_qc_dev *dev = netdev_priv(net);
strlcpy(p->driver, "g_qc_ether", sizeof p->driver);
strlcpy(p->version, UETH__VERSION, sizeof p->version);
strlcpy(p->fw_version, dev->gadget->name, sizeof p->fw_version);
strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof p->bus_info);
}
static const struct ethtool_ops qc_ethtool_ops = {
.get_drvinfo = eth_qc_get_drvinfo,
.get_link = ethtool_op_get_link,
};
static netdev_tx_t eth_qc_start_xmit(struct sk_buff *skb,
struct net_device *net)
{
return NETDEV_TX_OK;
}
static int eth_qc_open(struct net_device *net)
{
struct eth_qc_dev *dev = netdev_priv(net);
struct qc_gether *link;
DBG(dev, "%s\n", __func__);
if (netif_carrier_ok(dev->net)) {
/* Force the netif to send the RTM_NEWLINK event
* that in use to notify on the USB cable status.
*/
netif_carrier_off(dev->net);
netif_carrier_on(dev->net);
netif_wake_queue(dev->net);
}
spin_lock_irq(&dev->lock);
link = dev->port_usb;
if (link && link->open)
link->open(link);
spin_unlock_irq(&dev->lock);
return 0;
}
static int eth_qc_stop(struct net_device *net)
{
struct eth_qc_dev *dev = netdev_priv(net);
unsigned long flags;
struct qc_gether *link = dev->port_usb;
VDBG(dev, "%s\n", __func__);
netif_stop_queue(net);
spin_lock_irqsave(&dev->lock, flags);
if (dev->port_usb && link->close)
link->close(link);
spin_unlock_irqrestore(&dev->lock, flags);
return 0;
}
/*-------------------------------------------------------------------------*/
/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */
static char *qc_dev_addr;
module_param(qc_dev_addr, charp, S_IRUGO);
MODULE_PARM_DESC(qc_dev_addr, "QC Device Ethernet Address");
/* this address is invisible to ifconfig */
static char *qc_host_addr;
module_param(qc_host_addr, charp, S_IRUGO);
MODULE_PARM_DESC(qc_host_addr, "QC Host Ethernet Address");
static int get_qc_ether_addr(const char *str, u8 *dev_addr)
{
if (str) {
unsigned i;
for (i = 0; i < 6; i++) {
unsigned char num;
if ((*str == '.') || (*str == ':'))
str++;
num = hex_to_bin(*str++) << 4;
num |= hex_to_bin(*str++);
dev_addr[i] = num;
}
if (is_valid_ether_addr(dev_addr))
return 0;
}
random_ether_addr(dev_addr);
return 1;
}
static const struct net_device_ops eth_qc_netdev_ops = {
.ndo_open = eth_qc_open,
.ndo_stop = eth_qc_stop,
.ndo_start_xmit = eth_qc_start_xmit,
.ndo_change_mtu = ueth_qc_change_mtu,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
};
static struct device_type qc_gadget_type = {
.name = "gadget",
};
void gether_qc_get_macs(u8 dev_mac[ETH_ALEN], u8 host_mac[ETH_ALEN])
{
if (get_qc_ether_addr(qc_dev_addr, dev_mac))
pr_debug("using random dev_mac ethernet address\n");
if (get_qc_ether_addr(qc_host_addr, host_mac))
pr_debug("using random host_mac ethernet address\n");
}
/**
* gether_qc_setup - initialize one ethernet-over-usb link
* @g: gadget to associated with these links
* @ethaddr: NULL, or a buffer in which the ethernet address of the
* host side of the link is recorded
* Context: may sleep
*
* This sets up the single network link that may be exported by a
* gadget driver using this framework. The link layer addresses are
* set up using module parameters.
*
* Returns negative errno, or zero on success
*/
int gether_qc_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN])
{
return gether_qc_setup_name(g, ethaddr, "usb");
}
/**
* gether_qc_setup_name - initialize one ethernet-over-usb link
* @g: gadget to associated with these links
* @ethaddr: NULL, or a buffer in which the ethernet address of the
* host side of the link is recorded
* @netname: name for network device (for example, "usb")
* Context: may sleep
*
* This sets up the single network link that may be exported by a
* gadget driver using this framework. The link layer addresses are
* set up using module parameters.
*
* Returns negative errno, or zero on success
*/
int gether_qc_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
const char *netname)
{
struct eth_qc_dev *dev;
struct net_device *net;
int status;
net = alloc_etherdev(sizeof *dev);
if (!net)
return -ENOMEM;
dev = netdev_priv(net);
spin_lock_init(&dev->lock);
/* network device setup */
dev->net = net;
snprintf(net->name, sizeof(net->name), "%s%%d", netname);
if (get_qc_ether_addr(qc_dev_addr, net->dev_addr))
dev_warn(&g->dev,
"using random %s ethernet address\n", "self");
if (get_qc_ether_addr(qc_host_addr, dev->host_mac))
dev_warn(&g->dev,
"using random %s ethernet address\n", "host");
if (ethaddr)
memcpy(ethaddr, dev->host_mac, ETH_ALEN);
net->netdev_ops = &eth_qc_netdev_ops;
2024-09-09 08:57:42 +00:00
net->ethtool_ops = &qc_ethtool_ops;
2024-09-09 08:52:07 +00:00
netif_carrier_off(net);
dev->gadget = g;
SET_NETDEV_DEV(net, &g->dev);
SET_NETDEV_DEVTYPE(net, &qc_gadget_type);
status = register_netdev(net);
if (status < 0) {
dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
free_netdev(net);
} else {
INFO(dev, "MAC %pM\n", net->dev_addr);
INFO(dev, "HOST MAC %pM\n", dev->host_mac);
}
return status;
}
/**
* gether_qc_cleanup_name - remove Ethernet-over-USB device
* @netname: name for network device (for example, "usb")
* Context: may sleep
*
* This is called to free all resources allocated by @gether_qc_setup().
*/
void gether_qc_cleanup_name(const char *netname)
{
struct net_device *net_dev;
/* Extract the eth_qc_dev from the net device */
net_dev = dev_get_by_name(&init_net, netname);
if (net_dev) {
dev_put(net_dev);
unregister_netdev(net_dev);
free_netdev(net_dev);
}
}
2024-09-09 08:57:42 +00:00
struct net_device *gether_qc_get_net(const char *netname)
{
struct net_device *net_dev;
net_dev = dev_get_by_name(&init_net, netname);
if (!net_dev)
return ERR_PTR(-EINVAL);
/*
* Decrement net_dev refcount as it was incremented in
* dev_get_by_name().
*/
dev_put(net_dev);
return net_dev;
}
2024-09-09 08:52:07 +00:00
/**
* gether_qc_connect_name - notify network layer that USB link
* is active
* @link: the USB link, set up with endpoints, descriptors matching
* current device speed, and any framing wrapper(s) set up.
* @netname: name for network device (for example, "usb")
* Context: irqs blocked
* @netif_enable: if true, net interface will be turned on
*
* This is called to let the network layer know the connection
* is active ("carrier detect").
*/
struct net_device *gether_qc_connect_name(struct qc_gether *link,
const char *netname, bool netif_enable)
{
struct net_device *net_dev;
struct eth_qc_dev *dev;
/* Extract the eth_qc_dev from the net device */
net_dev = dev_get_by_name(&init_net, netname);
if (!net_dev)
return ERR_PTR(-EINVAL);
dev_put(net_dev);
dev = netdev_priv(net_dev);
if (!dev)
return ERR_PTR(-EINVAL);
dev->zlp = link->is_zlp_ok;
dev->header_len = link->header_len;
spin_lock(&dev->lock);
dev->port_usb = link;
link->ioport = dev;
if (netif_running(dev->net)) {
if (link->open)
link->open(link);
} else {
if (link->close)
link->close(link);
}
spin_unlock(&dev->lock);
if (netif_enable) {
netif_carrier_on(dev->net);
if (netif_running(dev->net))
netif_wake_queue(dev->net);
}
return dev->net;
}
/**
* gether_qc_disconnect_name - notify network layer that USB
* link is inactive
* @link: the USB link, on which gether_connect() was called
* @netname: name for network device (for example, "usb")
* Context: irqs blocked
*
* This is called to let the network layer know the connection
* went inactive ("no carrier").
*
* On return, the state is as if gether_connect() had never been called.
*/
void gether_qc_disconnect_name(struct qc_gether *link, const char *netname)
{
struct net_device *net_dev;
struct eth_qc_dev *dev;
/* Extract the eth_qc_dev from the net device */
net_dev = dev_get_by_name(&init_net, netname);
if (!net_dev)
return;
dev_put(net_dev);
dev = netdev_priv(net_dev);
if (!dev)
return;
DBG(dev, "%s\n", __func__);
netif_stop_queue(dev->net);
netif_carrier_off(dev->net);
spin_lock(&dev->lock);
dev->port_usb = NULL;
link->ioport = NULL;
spin_unlock(&dev->lock);
}