This patch adds support for pushing a MAC address out to USB based ethernet controllers driven by cdc_ncm. With this change, ifconfig can now set the device's MAC address. For example, the Dell Universal Dock D6000 is driven by cdc_ncm. The D6000 can now have its MAC address set by ifconfig, as it can be done in Windows. This was tested with a D6000 using ifconfig on an x86 based chromebook, where iproute2 is not available. Signed-off-by: Charles Hyde <charles.hyde@xxxxxxxxxxxx> Cc: Mario Limonciello <mario.limonciello@xxxxxxxx> Cc: Oliver Neukum <oliver@xxxxxxxxxx> Cc: linux-usb@xxxxxxxxxxxxxxx --- drivers/net/usb/cdc_ncm.c | 74 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 50c05d0f44cb..6fbe88fd7896 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -750,6 +750,78 @@ int cdc_ncm_change_mtu(struct net_device *net, int new_mtu) } EXPORT_SYMBOL_GPL(cdc_ncm_change_mtu); +/* Provide method to get MAC address from the USB device's ethernet controller. + * If the device supports CDC_GET_ADDRESS, then we should receive just six bytes. + * Otherwise, use the prior method by asking for the descriptor. + */ +static int cdc_ncm_get_ethernet_address(struct usbnet *dev, struct cdc_ncm_ctx *ctx) +{ + int ret; + char * buf; + + buf = kmalloc(ETH_ALEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + USB_CDC_GET_NET_ADDRESS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, USB_REQ_SET_ADDRESS, buf, ETH_ALEN, + USB_CTRL_GET_TIMEOUT); + if (ret == ETH_ALEN) { + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + ret = 0; /* success */ + } else { + ret = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress); + } + kfree(buf); + return ret; +} + +/* Provide method to push MAC address to the USB device's ethernet controller. + * If the device does not support CDC_SET_ADDRESS, then there is no harm and + * we proceed as before. + */ +static int cdc_ncm_set_ethernet_address(struct usbnet *dev, struct sockaddr *addr) +{ + int ret; + char * buf; + + buf = kmalloc(ETH_ALEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, addr->sa_data, ETH_ALEN); + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + USB_CDC_SET_NET_ADDRESS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, USB_REQ_SET_ADDRESS, buf, ETH_ALEN, + USB_CTRL_GET_TIMEOUT); + if (ret == ETH_ALEN) + ret = 0; /* success */ + else if (ret < 0) { + dev_dbg(&dev->udev->dev, "bad MAC address put, %d\n", ret); + } + kfree(buf); + return ret; +} + +/* Provide method to push MAC address to the USB device's ethernet controller. + */ +int cdc_ncm_set_mac_addr(struct net_device *net, void *p) +{ + struct usbnet *dev = netdev_priv(net); + + /* + * Try to push the MAC address out to the device. Ignore any errors, + * to be compatible with prior versions of this source. + */ + cdc_ncm_set_ethernet_address(dev, (struct sockaddr *)p); + + return eth_mac_addr(net, p); +} +EXPORT_SYMBOL_GPL(cdc_ncm_set_mac_addr); + static const struct net_device_ops cdc_ncm_netdev_ops = { .ndo_open = usbnet_open, .ndo_stop = usbnet_stop, @@ -757,7 +829,7 @@ static const struct net_device_ops cdc_ncm_netdev_ops = { .ndo_tx_timeout = usbnet_tx_timeout, .ndo_get_stats64 = usbnet_get_stats64, .ndo_change_mtu = cdc_ncm_change_mtu, - .ndo_set_mac_address = eth_mac_addr, + .ndo_set_mac_address = cdc_ncm_set_mac_addr, .ndo_validate_addr = eth_validate_addr, }; -- 2.20.1