diff options
Diffstat (limited to 'drivers/usb/gadget/f_acm.c')
| -rw-r--r-- | drivers/usb/gadget/f_acm.c | 308 | 
1 files changed, 186 insertions, 122 deletions
diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index bd6226cbae8..ab1065afbbd 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -5,7 +5,7 @@   * Copyright (C) 2008 by David Brownell   * Copyright (C) 2008 by Nokia Corporation   * Copyright (C) 2009 by Samsung Electronics - * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) + * Author: Michal Nazarewicz (mina86@mina86.com)   *   * This software is distributed under the terms of the GNU General   * Public License ("GPL") as published by the Free Software Foundation, @@ -16,7 +16,9 @@  #include <linux/slab.h>  #include <linux/kernel.h> +#include <linux/module.h>  #include <linux/device.h> +#include <linux/err.h>  #include "u_serial.h"  #include "gadget_chips.h" @@ -39,12 +41,6 @@   * descriptors (roughly equivalent to CDC Unions) may sometimes help.   */ -struct acm_ep_descs { -	struct usb_endpoint_descriptor	*in; -	struct usb_endpoint_descriptor	*out; -	struct usb_endpoint_descriptor	*notify; -}; -  struct f_acm {  	struct gserial			port;  	u8				ctrl_id, data_id; @@ -58,11 +54,7 @@ struct f_acm {  	 */  	spinlock_t			lock; -	struct acm_ep_descs		fs; -	struct acm_ep_descs		hs; -  	struct usb_ep			*notify; -	struct usb_endpoint_descriptor	*notify_desc;  	struct usb_request		*notify_req;  	struct usb_cdc_line_coding	port_line_coding;	/* 8-N-1 etc */ @@ -97,7 +89,7 @@ static inline struct f_acm *port_to_acm(struct gserial *p)  /* notification endpoint uses smallish and infrequent fixed-size messages */ -#define GS_LOG2_NOTIFY_INTERVAL		5	/* 1 << 5 == 32 msec */ +#define GS_NOTIFY_INTERVAL_MS		32  #define GS_NOTIFY_MAXPACKET		10	/* notification + 2 bytes */  /* interface and class descriptors: */ @@ -177,7 +169,7 @@ static struct usb_endpoint_descriptor acm_fs_notify_desc = {  	.bEndpointAddress =	USB_DIR_IN,  	.bmAttributes =		USB_ENDPOINT_XFER_INT,  	.wMaxPacketSize =	cpu_to_le16(GS_NOTIFY_MAXPACKET), -	.bInterval =		1 << GS_LOG2_NOTIFY_INTERVAL, +	.bInterval =		GS_NOTIFY_INTERVAL_MS,  };  static struct usb_endpoint_descriptor acm_fs_in_desc = { @@ -209,14 +201,13 @@ static struct usb_descriptor_header *acm_fs_function[] = {  };  /* high speed support: */ -  static struct usb_endpoint_descriptor acm_hs_notify_desc = {  	.bLength =		USB_DT_ENDPOINT_SIZE,  	.bDescriptorType =	USB_DT_ENDPOINT,  	.bEndpointAddress =	USB_DIR_IN,  	.bmAttributes =		USB_ENDPOINT_XFER_INT,  	.wMaxPacketSize =	cpu_to_le16(GS_NOTIFY_MAXPACKET), -	.bInterval =		GS_LOG2_NOTIFY_INTERVAL+4, +	.bInterval =		USB_MS_TO_HS_INTERVAL(GS_NOTIFY_INTERVAL_MS),  };  static struct usb_endpoint_descriptor acm_hs_in_desc = { @@ -247,6 +238,42 @@ static struct usb_descriptor_header *acm_hs_function[] = {  	NULL,  }; +static struct usb_endpoint_descriptor acm_ss_in_desc = { +	.bLength =		USB_DT_ENDPOINT_SIZE, +	.bDescriptorType =	USB_DT_ENDPOINT, +	.bmAttributes =		USB_ENDPOINT_XFER_BULK, +	.wMaxPacketSize =	cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor acm_ss_out_desc = { +	.bLength =		USB_DT_ENDPOINT_SIZE, +	.bDescriptorType =	USB_DT_ENDPOINT, +	.bmAttributes =		USB_ENDPOINT_XFER_BULK, +	.wMaxPacketSize =	cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor acm_ss_bulk_comp_desc = { +	.bLength =              sizeof acm_ss_bulk_comp_desc, +	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *acm_ss_function[] = { +	(struct usb_descriptor_header *) &acm_iad_descriptor, +	(struct usb_descriptor_header *) &acm_control_interface_desc, +	(struct usb_descriptor_header *) &acm_header_desc, +	(struct usb_descriptor_header *) &acm_call_mgmt_descriptor, +	(struct usb_descriptor_header *) &acm_descriptor, +	(struct usb_descriptor_header *) &acm_union_desc, +	(struct usb_descriptor_header *) &acm_hs_notify_desc, +	(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, +	(struct usb_descriptor_header *) &acm_data_interface_desc, +	(struct usb_descriptor_header *) &acm_ss_in_desc, +	(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, +	(struct usb_descriptor_header *) &acm_ss_out_desc, +	(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, +	NULL, +}; +  /* string descriptors: */  #define ACM_CTRL_IDX	0 @@ -258,7 +285,7 @@ static struct usb_string acm_string_defs[] = {  	[ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)",  	[ACM_DATA_IDX].s = "CDC ACM Data",  	[ACM_IAD_IDX ].s = "CDC Serial", -	{  /* ZEROES END LIST */ }, +	{  } /* end of list */  };  static struct usb_gadget_strings acm_string_table = { @@ -405,23 +432,27 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)  			usb_ep_disable(acm->notify);  		} else {  			VDBG(cdev, "init acm ctrl interface %d\n", intf); -			acm->notify_desc = ep_choose(cdev->gadget, -					acm->hs.notify, -					acm->fs.notify); +			if (config_ep_by_speed(cdev->gadget, f, acm->notify)) +				return -EINVAL;  		} -		usb_ep_enable(acm->notify, acm->notify_desc); +		usb_ep_enable(acm->notify);  		acm->notify->driver_data = acm;  	} else if (intf == acm->data_id) {  		if (acm->port.in->driver_data) {  			DBG(cdev, "reset acm ttyGS%d\n", acm->port_num);  			gserial_disconnect(&acm->port); -		} else { +		} +		if (!acm->port.in->desc || !acm->port.out->desc) {  			DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); -			acm->port.in_desc = ep_choose(cdev->gadget, -					acm->hs.in, acm->fs.in); -			acm->port.out_desc = ep_choose(cdev->gadget, -					acm->hs.out, acm->fs.out); +			if (config_ep_by_speed(cdev->gadget, f, +					       acm->port.in) || +			    config_ep_by_speed(cdev->gadget, f, +					       acm->port.out)) { +				acm->port.in->desc = NULL; +				acm->port.out->desc = NULL; +				return -EINVAL; +			}  		}  		gserial_connect(&acm->port, acm->port_num); @@ -576,9 +607,23 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)  {  	struct usb_composite_dev *cdev = c->cdev;  	struct f_acm		*acm = func_to_acm(f); +	struct usb_string	*us;  	int			status;  	struct usb_ep		*ep; +	/* REVISIT might want instance-specific strings to help +	 * distinguish instances ... +	 */ + +	/* maybe allocate device-global string IDs, and patch descriptors */ +	us = usb_gstrings_attach(cdev, acm_strings, +			ARRAY_SIZE(acm_string_defs)); +	if (IS_ERR(us)) +		return PTR_ERR(us); +	acm_control_interface_desc.iInterface = us[ACM_CTRL_IDX].id; +	acm_data_interface_desc.iInterface = us[ACM_DATA_IDX].id; +	acm_iad_descriptor.iFunction = us[ACM_IAD_IDX].id; +  	/* allocate instance-specific interface IDs, and patch descriptors */  	status = usb_interface_id(c, f);  	if (status < 0) @@ -629,43 +674,26 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)  	acm->notify_req->complete = acm_cdc_notify_complete;  	acm->notify_req->context = acm; -	/* copy descriptors, and track endpoint copies */ -	f->descriptors = usb_copy_descriptors(acm_fs_function); -	if (!f->descriptors) -		goto fail; - -	acm->fs.in = usb_find_endpoint(acm_fs_function, -			f->descriptors, &acm_fs_in_desc); -	acm->fs.out = usb_find_endpoint(acm_fs_function, -			f->descriptors, &acm_fs_out_desc); -	acm->fs.notify = usb_find_endpoint(acm_fs_function, -			f->descriptors, &acm_fs_notify_desc); -  	/* support all relevant hardware speeds... we expect that when  	 * hardware is dual speed, all bulk-capable endpoints work at  	 * both speeds  	 */ -	if (gadget_is_dualspeed(c->cdev->gadget)) { -		acm_hs_in_desc.bEndpointAddress = -				acm_fs_in_desc.bEndpointAddress; -		acm_hs_out_desc.bEndpointAddress = -				acm_fs_out_desc.bEndpointAddress; -		acm_hs_notify_desc.bEndpointAddress = -				acm_fs_notify_desc.bEndpointAddress; - -		/* copy descriptors, and track endpoint copies */ -		f->hs_descriptors = usb_copy_descriptors(acm_hs_function); - -		acm->hs.in = usb_find_endpoint(acm_hs_function, -				f->hs_descriptors, &acm_hs_in_desc); -		acm->hs.out = usb_find_endpoint(acm_hs_function, -				f->hs_descriptors, &acm_hs_out_desc); -		acm->hs.notify = usb_find_endpoint(acm_hs_function, -				f->hs_descriptors, &acm_hs_notify_desc); -	} +	acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; +	acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; +	acm_hs_notify_desc.bEndpointAddress = +		acm_fs_notify_desc.bEndpointAddress; + +	acm_ss_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; +	acm_ss_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; + +	status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, +			acm_ss_function); +	if (status) +		goto fail;  	DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",  			acm->port_num, +			gadget_is_superspeed(c->cdev->gadget) ? "super" :  			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",  			acm->port.in->name, acm->port.out->name,  			acm->notify->name); @@ -688,82 +716,34 @@ fail:  	return status;  } -static void -acm_unbind(struct usb_configuration *c, struct usb_function *f) +static void acm_unbind(struct usb_configuration *c, struct usb_function *f)  {  	struct f_acm		*acm = func_to_acm(f); -	if (gadget_is_dualspeed(c->cdev->gadget)) -		usb_free_descriptors(f->hs_descriptors); -	usb_free_descriptors(f->descriptors); -	gs_free_req(acm->notify, acm->notify_req); -	kfree(acm); +	acm_string_defs[0].id = 0; +	usb_free_all_descriptors(f); +	if (acm->notify_req) +		gs_free_req(acm->notify, acm->notify_req);  } -/* Some controllers can't support CDC ACM ... */ -static inline bool can_support_cdc(struct usb_configuration *c) +static void acm_free_func(struct usb_function *f)  { -	/* everything else is *probably* fine ... */ -	return true; +	struct f_acm		*acm = func_to_acm(f); + +	kfree(acm);  } -/** - * acm_bind_config - add a CDC ACM function to a configuration - * @c: the configuration to support the CDC ACM instance - * @port_num: /dev/ttyGS* port this interface will use - * Context: single threaded during gadget setup - * - * Returns zero on success, else negative errno. - * - * Caller must have called @gserial_setup() with enough ports to - * handle all the ones it binds.  Caller is also responsible - * for calling @gserial_cleanup() before module unload. - */ -int acm_bind_config(struct usb_configuration *c, u8 port_num) +static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)  { -	struct f_acm	*acm; -	int		status; - -	if (!can_support_cdc(c)) -		return -EINVAL; - -	/* REVISIT might want instance-specific strings to help -	 * distinguish instances ... -	 */ - -	/* maybe allocate device-global string IDs, and patch descriptors */ -	if (acm_string_defs[ACM_CTRL_IDX].id == 0) { -		status = usb_string_id(c->cdev); -		if (status < 0) -			return status; -		acm_string_defs[ACM_CTRL_IDX].id = status; - -		acm_control_interface_desc.iInterface = status; - -		status = usb_string_id(c->cdev); -		if (status < 0) -			return status; -		acm_string_defs[ACM_DATA_IDX].id = status; - -		acm_data_interface_desc.iInterface = status; - -		status = usb_string_id(c->cdev); -		if (status < 0) -			return status; -		acm_string_defs[ACM_IAD_IDX].id = status; +	struct f_serial_opts *opts; +	struct f_acm *acm; -		acm_iad_descriptor.iFunction = status; -	} - -	/* allocate and initialize one new instance */ -	acm = kzalloc(sizeof *acm, GFP_KERNEL); +	acm = kzalloc(sizeof(*acm), GFP_KERNEL);  	if (!acm) -		return -ENOMEM; +		return ERR_PTR(-ENOMEM);  	spin_lock_init(&acm->lock); -	acm->port_num = port_num; -  	acm->port.connect = acm_connect;  	acm->port.disconnect = acm_disconnect;  	acm->port.send_break = acm_send_break; @@ -772,13 +752,97 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num)  	acm->port.func.strings = acm_strings;  	/* descriptors are per-instance copies */  	acm->port.func.bind = acm_bind; -	acm->port.func.unbind = acm_unbind;  	acm->port.func.set_alt = acm_set_alt;  	acm->port.func.setup = acm_setup;  	acm->port.func.disable = acm_disable; -	status = usb_add_function(c, &acm->port.func); -	if (status) -		kfree(acm); -	return status; +	opts = container_of(fi, struct f_serial_opts, func_inst); +	acm->port_num = opts->port_num; +	acm->port.func.unbind = acm_unbind; +	acm->port.func.free_func = acm_free_func; + +	return &acm->port.func; +} + +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ +	return container_of(to_config_group(item), struct f_serial_opts, +			func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_serial_opts); +static ssize_t f_acm_attr_show(struct config_item *item, +				 struct configfs_attribute *attr, +				 char *page) +{ +	struct f_serial_opts *opts = to_f_serial_opts(item); +	struct f_serial_opts_attribute *f_serial_opts_attr = +		container_of(attr, struct f_serial_opts_attribute, attr); +	ssize_t ret = 0; + +	if (f_serial_opts_attr->show) +		ret = f_serial_opts_attr->show(opts, page); +	return ret; +} + +static void acm_attr_release(struct config_item *item) +{ +	struct f_serial_opts *opts = to_f_serial_opts(item); + +	usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations acm_item_ops = { +	.release                = acm_attr_release, +	.show_attribute		= f_acm_attr_show, +}; + +static ssize_t f_acm_port_num_show(struct f_serial_opts *opts, char *page) +{ +	return sprintf(page, "%u\n", opts->port_num); +} + +static struct f_serial_opts_attribute f_acm_port_num = +	__CONFIGFS_ATTR_RO(port_num, f_acm_port_num_show); + + +static struct configfs_attribute *acm_attrs[] = { +	&f_acm_port_num.attr, +	NULL, +}; + +static struct config_item_type acm_func_type = { +	.ct_item_ops    = &acm_item_ops, +	.ct_attrs	= acm_attrs, +	.ct_owner       = THIS_MODULE, +}; + +static void acm_free_instance(struct usb_function_instance *fi) +{ +	struct f_serial_opts *opts; + +	opts = container_of(fi, struct f_serial_opts, func_inst); +	gserial_free_line(opts->port_num); +	kfree(opts); +} + +static struct usb_function_instance *acm_alloc_instance(void) +{ +	struct f_serial_opts *opts; +	int ret; + +	opts = kzalloc(sizeof(*opts), GFP_KERNEL); +	if (!opts) +		return ERR_PTR(-ENOMEM); +	opts->func_inst.free_func_inst = acm_free_instance; +	ret = gserial_alloc_line(&opts->port_num); +	if (ret) { +		kfree(opts); +		return ERR_PTR(ret); +	} +	config_group_init_type_name(&opts->func_inst.group, "", +			&acm_func_type); +	return &opts->func_inst;  } +DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func); +MODULE_LICENSE("GPL");  | 
