![Alan Stern](/assets/img/avatar_default.png)
This patch (as1185) makes usbcore take advantage of the bus notifications sent out by the driver core. Now we can create all our device and interface attribute files before the device or interface uevent is broadcast. A side effect is that we no longer create the endpoint "pseudo" devices at the same time as a device or interface is registered -- it seems like a bad idea to try registering an endpoint before the registration of its parent is complete. So the routines for creating and removing endpoint devices have been split out and renamed, and they are called explicitly when needed. A new bitflag is used for keeping track of whether or not the interface's endpoint devices have been created, since (just as with the interface attributes) they vary with the altsetting and hence can be changed at random times. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Cc: Kay Sievers <kay.sievers@vrfy.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
357 lines
8.1 KiB
C
357 lines
8.1 KiB
C
/*
|
|
* drivers/usb/core/endpoint.c
|
|
*
|
|
* (C) Copyright 2002,2004,2006 Greg Kroah-Hartman
|
|
* (C) Copyright 2002,2004 IBM Corp.
|
|
* (C) Copyright 2006 Novell Inc.
|
|
*
|
|
* Endpoint sysfs stuff
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/usb.h>
|
|
#include "usb.h"
|
|
|
|
#define MAX_ENDPOINT_MINORS (64*128*32)
|
|
static int usb_endpoint_major;
|
|
static DEFINE_IDR(endpoint_idr);
|
|
|
|
struct ep_device {
|
|
struct usb_endpoint_descriptor *desc;
|
|
struct usb_device *udev;
|
|
struct device dev;
|
|
int minor;
|
|
};
|
|
#define to_ep_device(_dev) \
|
|
container_of(_dev, struct ep_device, dev)
|
|
|
|
struct ep_attribute {
|
|
struct attribute attr;
|
|
ssize_t (*show)(struct usb_device *,
|
|
struct usb_endpoint_descriptor *, char *);
|
|
};
|
|
#define to_ep_attribute(_attr) \
|
|
container_of(_attr, struct ep_attribute, attr)
|
|
|
|
#define usb_ep_attr(field, format_string) \
|
|
static ssize_t show_ep_##field(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
char *buf) \
|
|
{ \
|
|
struct ep_device *ep = to_ep_device(dev); \
|
|
return sprintf(buf, format_string, ep->desc->field); \
|
|
} \
|
|
static DEVICE_ATTR(field, S_IRUGO, show_ep_##field, NULL);
|
|
|
|
usb_ep_attr(bLength, "%02x\n")
|
|
usb_ep_attr(bEndpointAddress, "%02x\n")
|
|
usb_ep_attr(bmAttributes, "%02x\n")
|
|
usb_ep_attr(bInterval, "%02x\n")
|
|
|
|
static ssize_t show_ep_wMaxPacketSize(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ep_device *ep = to_ep_device(dev);
|
|
return sprintf(buf, "%04x\n",
|
|
le16_to_cpu(ep->desc->wMaxPacketSize) & 0x07ff);
|
|
}
|
|
static DEVICE_ATTR(wMaxPacketSize, S_IRUGO, show_ep_wMaxPacketSize, NULL);
|
|
|
|
static ssize_t show_ep_type(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct ep_device *ep = to_ep_device(dev);
|
|
char *type = "unknown";
|
|
|
|
switch (ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
|
|
case USB_ENDPOINT_XFER_CONTROL:
|
|
type = "Control";
|
|
break;
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
type = "Isoc";
|
|
break;
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
type = "Bulk";
|
|
break;
|
|
case USB_ENDPOINT_XFER_INT:
|
|
type = "Interrupt";
|
|
break;
|
|
}
|
|
return sprintf(buf, "%s\n", type);
|
|
}
|
|
static DEVICE_ATTR(type, S_IRUGO, show_ep_type, NULL);
|
|
|
|
static ssize_t show_ep_interval(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ep_device *ep = to_ep_device(dev);
|
|
char unit;
|
|
unsigned interval = 0;
|
|
unsigned in;
|
|
|
|
in = (ep->desc->bEndpointAddress & USB_DIR_IN);
|
|
|
|
switch (ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
|
|
case USB_ENDPOINT_XFER_CONTROL:
|
|
if (ep->udev->speed == USB_SPEED_HIGH) /* uframes per NAK */
|
|
interval = ep->desc->bInterval;
|
|
break;
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
interval = 1 << (ep->desc->bInterval - 1);
|
|
break;
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
if (ep->udev->speed == USB_SPEED_HIGH && !in) /* uframes per NAK */
|
|
interval = ep->desc->bInterval;
|
|
break;
|
|
case USB_ENDPOINT_XFER_INT:
|
|
if (ep->udev->speed == USB_SPEED_HIGH)
|
|
interval = 1 << (ep->desc->bInterval - 1);
|
|
else
|
|
interval = ep->desc->bInterval;
|
|
break;
|
|
}
|
|
interval *= (ep->udev->speed == USB_SPEED_HIGH) ? 125 : 1000;
|
|
if (interval % 1000)
|
|
unit = 'u';
|
|
else {
|
|
unit = 'm';
|
|
interval /= 1000;
|
|
}
|
|
|
|
return sprintf(buf, "%d%cs\n", interval, unit);
|
|
}
|
|
static DEVICE_ATTR(interval, S_IRUGO, show_ep_interval, NULL);
|
|
|
|
static ssize_t show_ep_direction(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ep_device *ep = to_ep_device(dev);
|
|
char *direction;
|
|
|
|
if ((ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
|
|
USB_ENDPOINT_XFER_CONTROL)
|
|
direction = "both";
|
|
else if (ep->desc->bEndpointAddress & USB_DIR_IN)
|
|
direction = "in";
|
|
else
|
|
direction = "out";
|
|
return sprintf(buf, "%s\n", direction);
|
|
}
|
|
static DEVICE_ATTR(direction, S_IRUGO, show_ep_direction, NULL);
|
|
|
|
static struct attribute *ep_dev_attrs[] = {
|
|
&dev_attr_bLength.attr,
|
|
&dev_attr_bEndpointAddress.attr,
|
|
&dev_attr_bmAttributes.attr,
|
|
&dev_attr_bInterval.attr,
|
|
&dev_attr_wMaxPacketSize.attr,
|
|
&dev_attr_interval.attr,
|
|
&dev_attr_type.attr,
|
|
&dev_attr_direction.attr,
|
|
NULL,
|
|
};
|
|
static struct attribute_group ep_dev_attr_grp = {
|
|
.attrs = ep_dev_attrs,
|
|
};
|
|
static struct attribute_group *ep_dev_groups[] = {
|
|
&ep_dev_attr_grp,
|
|
NULL
|
|
};
|
|
|
|
static int usb_endpoint_major_init(void)
|
|
{
|
|
dev_t dev;
|
|
int error;
|
|
|
|
error = alloc_chrdev_region(&dev, 0, MAX_ENDPOINT_MINORS,
|
|
"usb_endpoint");
|
|
if (error) {
|
|
printk(KERN_ERR "Unable to get a dynamic major for "
|
|
"usb endpoints.\n");
|
|
return error;
|
|
}
|
|
usb_endpoint_major = MAJOR(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static void usb_endpoint_major_cleanup(void)
|
|
{
|
|
unregister_chrdev_region(MKDEV(usb_endpoint_major, 0),
|
|
MAX_ENDPOINT_MINORS);
|
|
}
|
|
|
|
static int endpoint_get_minor(struct ep_device *ep_dev)
|
|
{
|
|
static DEFINE_MUTEX(minor_lock);
|
|
int retval = -ENOMEM;
|
|
int id;
|
|
|
|
mutex_lock(&minor_lock);
|
|
if (idr_pre_get(&endpoint_idr, GFP_KERNEL) == 0)
|
|
goto exit;
|
|
|
|
retval = idr_get_new(&endpoint_idr, ep_dev, &id);
|
|
if (retval < 0) {
|
|
if (retval == -EAGAIN)
|
|
retval = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
ep_dev->minor = id & MAX_ID_MASK;
|
|
exit:
|
|
mutex_unlock(&minor_lock);
|
|
return retval;
|
|
}
|
|
|
|
static void endpoint_free_minor(struct ep_device *ep_dev)
|
|
{
|
|
idr_remove(&endpoint_idr, ep_dev->minor);
|
|
}
|
|
|
|
static struct endpoint_class {
|
|
struct kref kref;
|
|
struct class *class;
|
|
} *ep_class;
|
|
|
|
static int init_endpoint_class(void)
|
|
{
|
|
int result = 0;
|
|
|
|
if (ep_class != NULL) {
|
|
kref_get(&ep_class->kref);
|
|
goto exit;
|
|
}
|
|
|
|
ep_class = kmalloc(sizeof(*ep_class), GFP_KERNEL);
|
|
if (!ep_class) {
|
|
result = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
kref_init(&ep_class->kref);
|
|
ep_class->class = class_create(THIS_MODULE, "usb_endpoint");
|
|
if (IS_ERR(ep_class->class)) {
|
|
result = PTR_ERR(ep_class->class);
|
|
goto class_create_error;
|
|
}
|
|
|
|
result = usb_endpoint_major_init();
|
|
if (result)
|
|
goto endpoint_major_error;
|
|
|
|
goto exit;
|
|
|
|
endpoint_major_error:
|
|
class_destroy(ep_class->class);
|
|
class_create_error:
|
|
kfree(ep_class);
|
|
ep_class = NULL;
|
|
exit:
|
|
return result;
|
|
}
|
|
|
|
static void release_endpoint_class(struct kref *kref)
|
|
{
|
|
/* Ok, we cheat as we know we only have one ep_class */
|
|
class_destroy(ep_class->class);
|
|
kfree(ep_class);
|
|
ep_class = NULL;
|
|
usb_endpoint_major_cleanup();
|
|
}
|
|
|
|
static void destroy_endpoint_class(void)
|
|
{
|
|
if (ep_class)
|
|
kref_put(&ep_class->kref, release_endpoint_class);
|
|
}
|
|
|
|
static void ep_device_release(struct device *dev)
|
|
{
|
|
struct ep_device *ep_dev = to_ep_device(dev);
|
|
|
|
endpoint_free_minor(ep_dev);
|
|
kfree(ep_dev);
|
|
}
|
|
|
|
int usb_create_ep_devs(struct device *parent,
|
|
struct usb_host_endpoint *endpoint,
|
|
struct usb_device *udev)
|
|
{
|
|
char name[8];
|
|
struct ep_device *ep_dev;
|
|
int retval;
|
|
|
|
retval = init_endpoint_class();
|
|
if (retval)
|
|
goto exit;
|
|
|
|
ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL);
|
|
if (!ep_dev) {
|
|
retval = -ENOMEM;
|
|
goto error_alloc;
|
|
}
|
|
|
|
retval = endpoint_get_minor(ep_dev);
|
|
if (retval) {
|
|
dev_err(parent, "can not allocate minor number for %s\n",
|
|
dev_name(&ep_dev->dev));
|
|
goto error_register;
|
|
}
|
|
|
|
ep_dev->desc = &endpoint->desc;
|
|
ep_dev->udev = udev;
|
|
ep_dev->dev.groups = ep_dev_groups;
|
|
ep_dev->dev.devt = MKDEV(usb_endpoint_major, ep_dev->minor);
|
|
ep_dev->dev.class = ep_class->class;
|
|
ep_dev->dev.parent = parent;
|
|
ep_dev->dev.release = ep_device_release;
|
|
dev_set_name(&ep_dev->dev, "usbdev%d.%d_ep%02x",
|
|
udev->bus->busnum, udev->devnum,
|
|
endpoint->desc.bEndpointAddress);
|
|
|
|
retval = device_register(&ep_dev->dev);
|
|
if (retval)
|
|
goto error_chrdev;
|
|
|
|
/* create the symlink to the old-style "ep_XX" directory */
|
|
sprintf(name, "ep_%02x", endpoint->desc.bEndpointAddress);
|
|
retval = sysfs_create_link(&parent->kobj, &ep_dev->dev.kobj, name);
|
|
if (retval)
|
|
goto error_link;
|
|
endpoint->ep_dev = ep_dev;
|
|
return retval;
|
|
|
|
error_link:
|
|
device_unregister(&ep_dev->dev);
|
|
destroy_endpoint_class();
|
|
return retval;
|
|
|
|
error_chrdev:
|
|
endpoint_free_minor(ep_dev);
|
|
|
|
error_register:
|
|
kfree(ep_dev);
|
|
error_alloc:
|
|
destroy_endpoint_class();
|
|
exit:
|
|
return retval;
|
|
}
|
|
|
|
void usb_remove_ep_devs(struct usb_host_endpoint *endpoint)
|
|
{
|
|
struct ep_device *ep_dev = endpoint->ep_dev;
|
|
|
|
if (ep_dev) {
|
|
char name[8];
|
|
|
|
sprintf(name, "ep_%02x", endpoint->desc.bEndpointAddress);
|
|
sysfs_remove_link(&ep_dev->dev.parent->kobj, name);
|
|
device_unregister(&ep_dev->dev);
|
|
endpoint->ep_dev = NULL;
|
|
destroy_endpoint_class();
|
|
}
|
|
}
|