aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/f_rndis.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/f_rndis.c')
-rw-r--r--drivers/usb/gadget/f_rndis.c265
1 files changed, 211 insertions, 54 deletions
diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c
index 71beeb83355..9c41e9515b8 100644
--- a/drivers/usb/gadget/f_rndis.c
+++ b/drivers/usb/gadget/f_rndis.c
@@ -17,14 +17,17 @@
#include <linux/slab.h>
#include <linux/kernel.h>
+#include <linux/module.h>
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/atomic.h>
#include "u_ether.h"
+#include "u_ether_configfs.h"
+#include "u_rndis.h"
#include "rndis.h"
-
+#include "configfs.h"
/*
* This function is an RNDIS Ethernet port -- a Microsoft protocol that's
@@ -375,7 +378,7 @@ static struct sk_buff *rndis_add_header(struct gether *port,
if (skb2)
rndis_add_hdr(skb2);
- dev_kfree_skb_any(skb);
+ dev_kfree_skb(skb);
return skb2;
}
@@ -447,14 +450,13 @@ static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req)
static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_rndis *rndis = req->context;
- struct usb_composite_dev *cdev = rndis->port.func.config->cdev;
int status;
/* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */
// spin_lock(&dev->lock);
status = rndis_msg_parser(rndis->config, (u8 *) req->buf);
if (status < 0)
- ERROR(cdev, "RNDIS command error %d, %d/%d\n",
+ pr_err("RNDIS command error %d, %d/%d\n",
status, req->actual, req->length);
// spin_unlock(&dev->lock);
}
@@ -656,6 +658,13 @@ static void rndis_close(struct gether *geth)
/*-------------------------------------------------------------------------*/
+/* Some controllers can't support RNDIS ... */
+static inline bool can_support_rndis(struct usb_configuration *c)
+{
+ /* everything else is *presumably* fine */
+ return true;
+}
+
/* ethernet function driver setup/binding */
static int
@@ -663,9 +672,51 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct f_rndis *rndis = func_to_rndis(f);
+ struct usb_string *us;
int status;
struct usb_ep *ep;
+ struct f_rndis_opts *rndis_opts;
+
+ if (!can_support_rndis(c))
+ return -EINVAL;
+
+ rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst);
+
+ if (cdev->use_os_string) {
+ f->os_desc_table = kzalloc(sizeof(*f->os_desc_table),
+ GFP_KERNEL);
+ if (!f->os_desc_table)
+ return -ENOMEM;
+ f->os_desc_n = 1;
+ f->os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc;
+ }
+
+ /*
+ * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+ * configurations are bound in sequence with list_for_each_entry,
+ * in each configuration its functions are bound in sequence
+ * with list_for_each_entry, so we assume no race condition
+ * with regard to rndis_opts->bound access
+ */
+ if (!rndis_opts->bound) {
+ gether_set_gadget(rndis_opts->net, cdev->gadget);
+ status = gether_register_netdev(rndis_opts->net);
+ if (status)
+ goto fail;
+ rndis_opts->bound = true;
+ }
+
+ us = usb_gstrings_attach(cdev, rndis_strings,
+ ARRAY_SIZE(rndis_string_defs));
+ if (IS_ERR(us)) {
+ status = PTR_ERR(us);
+ goto fail;
+ }
+ rndis_control_intf.iInterface = us[0].id;
+ rndis_data_intf.iInterface = us[1].id;
+ rndis_iad_descriptor.iFunction = us[2].id;
+
/* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
@@ -742,11 +793,6 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
rndis->port.open = rndis_open;
rndis->port.close = rndis_close;
- status = rndis_register(rndis_response_available, rndis);
- if (status < 0)
- goto fail;
- rndis->config = status;
-
rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0);
rndis_set_host_mac(rndis->config, rndis->ethaddr);
@@ -768,6 +814,8 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
return 0;
fail:
+ kfree(f->os_desc_table);
+ f->os_desc_n = 0;
usb_free_all_descriptors(f);
if (rndis->notify_req) {
@@ -788,65 +836,149 @@ fail:
return status;
}
-static void
-rndis_unbind(struct usb_configuration *c, struct usb_function *f)
+void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net)
{
- struct f_rndis *rndis = func_to_rndis(f);
+ struct f_rndis_opts *opts;
- rndis_deregister(rndis->config);
- rndis_exit();
+ opts = container_of(f, struct f_rndis_opts, func_inst);
+ if (opts->bound)
+ gether_cleanup(netdev_priv(opts->net));
+ else
+ free_netdev(opts->net);
+ opts->borrowed_net = opts->bound = true;
+ opts->net = net;
+}
+EXPORT_SYMBOL_GPL(rndis_borrow_net);
- rndis_string_defs[0].id = 0;
- usb_free_all_descriptors(f);
+static inline struct f_rndis_opts *to_f_rndis_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_rndis_opts,
+ func_inst.group);
+}
- kfree(rndis->notify_req->buf);
- usb_ep_free_request(rndis->notify, rndis->notify_req);
+/* f_rndis_item_ops */
+USB_ETHERNET_CONFIGFS_ITEM(rndis);
- kfree(rndis);
+/* f_rndis_opts_dev_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(rndis);
+
+/* f_rndis_opts_host_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(rndis);
+
+/* f_rndis_opts_qmult */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(rndis);
+
+/* f_rndis_opts_ifname */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(rndis);
+
+static struct configfs_attribute *rndis_attrs[] = {
+ &f_rndis_opts_dev_addr.attr,
+ &f_rndis_opts_host_addr.attr,
+ &f_rndis_opts_qmult.attr,
+ &f_rndis_opts_ifname.attr,
+ NULL,
+};
+
+static struct config_item_type rndis_func_type = {
+ .ct_item_ops = &rndis_item_ops,
+ .ct_attrs = rndis_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static void rndis_free_inst(struct usb_function_instance *f)
+{
+ struct f_rndis_opts *opts;
+
+ opts = container_of(f, struct f_rndis_opts, func_inst);
+ if (!opts->borrowed_net) {
+ if (opts->bound)
+ gether_cleanup(netdev_priv(opts->net));
+ else
+ free_netdev(opts->net);
+ }
+
+ kfree(opts->rndis_os_desc.group.default_groups); /* single VLA chunk */
+ kfree(opts);
}
-/* Some controllers can't support RNDIS ... */
-static inline bool can_support_rndis(struct usb_configuration *c)
+static struct usb_function_instance *rndis_alloc_inst(void)
{
- /* everything else is *presumably* fine */
- return true;
+ struct f_rndis_opts *opts;
+ struct usb_os_desc *descs[1];
+ char *names[1];
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return ERR_PTR(-ENOMEM);
+ opts->rndis_os_desc.ext_compat_id = opts->rndis_ext_compat_id;
+
+ mutex_init(&opts->lock);
+ opts->func_inst.free_func_inst = rndis_free_inst;
+ opts->net = gether_setup_default();
+ if (IS_ERR(opts->net)) {
+ struct net_device *net = opts->net;
+ kfree(opts);
+ return ERR_CAST(net);
+ }
+ INIT_LIST_HEAD(&opts->rndis_os_desc.ext_prop);
+
+ descs[0] = &opts->rndis_os_desc;
+ names[0] = "rndis";
+ usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs,
+ names, THIS_MODULE);
+ config_group_init_type_name(&opts->func_inst.group, "",
+ &rndis_func_type);
+
+ return &opts->func_inst;
}
-int
-rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
- u32 vendorID, const char *manufacturer)
+static void rndis_free(struct usb_function *f)
{
- struct f_rndis *rndis;
- int status;
+ struct f_rndis *rndis;
+ struct f_rndis_opts *opts;
- if (!can_support_rndis(c) || !ethaddr)
- return -EINVAL;
+ rndis = func_to_rndis(f);
+ rndis_deregister(rndis->config);
+ opts = container_of(f->fi, struct f_rndis_opts, func_inst);
+ kfree(rndis);
+ mutex_lock(&opts->lock);
+ opts->refcnt--;
+ mutex_unlock(&opts->lock);
+}
- if (rndis_string_defs[0].id == 0) {
- /* ... and setup RNDIS itself */
- status = rndis_init();
- if (status < 0)
- return status;
+static void rndis_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_rndis *rndis = func_to_rndis(f);
- status = usb_string_ids_tab(c->cdev, rndis_string_defs);
- if (status)
- return status;
+ kfree(f->os_desc_table);
+ f->os_desc_n = 0;
+ usb_free_all_descriptors(f);
- rndis_control_intf.iInterface = rndis_string_defs[0].id;
- rndis_data_intf.iInterface = rndis_string_defs[1].id;
- rndis_iad_descriptor.iFunction = rndis_string_defs[2].id;
- }
+ kfree(rndis->notify_req->buf);
+ usb_ep_free_request(rndis->notify, rndis->notify_req);
+}
+
+static struct usb_function *rndis_alloc(struct usb_function_instance *fi)
+{
+ struct f_rndis *rndis;
+ struct f_rndis_opts *opts;
+ int status;
/* allocate and initialize one new instance */
- status = -ENOMEM;
- rndis = kzalloc(sizeof *rndis, GFP_KERNEL);
+ rndis = kzalloc(sizeof(*rndis), GFP_KERNEL);
if (!rndis)
- goto fail;
+ return ERR_PTR(-ENOMEM);
+
+ opts = container_of(fi, struct f_rndis_opts, func_inst);
+ mutex_lock(&opts->lock);
+ opts->refcnt++;
- memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);
- rndis->vendorID = vendorID;
- rndis->manufacturer = manufacturer;
+ gether_get_host_addr_u8(opts->net, rndis->ethaddr);
+ rndis->vendorID = opts->vendor_id;
+ rndis->manufacturer = opts->manufacturer;
+ rndis->port.ioport = netdev_priv(opts->net);
+ mutex_unlock(&opts->lock);
/* RNDIS activates when the host changes this filter */
rndis->port.cdc_filter = 0;
@@ -856,19 +988,44 @@ rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
rndis->port.unwrap = rndis_rm_hdr;
rndis->port.func.name = "rndis";
- rndis->port.func.strings = rndis_strings;
/* descriptors are per-instance copies */
rndis->port.func.bind = rndis_bind;
rndis->port.func.unbind = rndis_unbind;
rndis->port.func.set_alt = rndis_set_alt;
rndis->port.func.setup = rndis_setup;
rndis->port.func.disable = rndis_disable;
+ rndis->port.func.free_func = rndis_free;
- status = usb_add_function(c, &rndis->port.func);
- if (status) {
+ status = rndis_register(rndis_response_available, rndis);
+ if (status < 0) {
kfree(rndis);
-fail:
- rndis_exit();
+ return ERR_PTR(status);
}
- return status;
+ rndis->config = status;
+
+ return &rndis->port.func;
}
+
+DECLARE_USB_FUNCTION(rndis, rndis_alloc_inst, rndis_alloc);
+
+static int __init rndis_mod_init(void)
+{
+ int ret;
+
+ ret = rndis_init();
+ if (ret)
+ return ret;
+
+ return usb_function_register(&rndisusb_func);
+}
+module_init(rndis_mod_init);
+
+static void __exit rndis_mod_exit(void)
+{
+ usb_function_unregister(&rndisusb_func);
+ rndis_exit();
+}
+module_exit(rndis_mod_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Brownell");