mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-24 14:10:49 +07:00
Merge git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb-2.6: (100 commits) usb-storage: revert DMA-alignment change for Wireless USB USB: use reset_resume when normal resume fails usb_gadget: composite cdc gadget fault handling usb gadget: minor USBCV fix for composite framework USB: Fix bug with byte order in isp116x-hcd.c fio write/read USB: fix double kfree in ipaq in error case USB: fix build error in cdc-acm for CONFIG_PM=n USB: remove board-specific UP2OCR configuration from pxa27x-udc USB: EHCI: Reconciling USB register differences on MPC85xx vs MPC83xx USB: Fix pointer/int cast in USB devio code usb gadget: g_cdc dependso on NET USB: Au1xxx-usb: suspend/resume support. USB: Au1xxx-usb: clean up ohci/ehci bus glue sources. usbfs: don't store bad pointers in registration usbfs: fix race between open and unregister usbfs: simplify the lookup-by-minor routines usbfs: send disconnect signals when device is unregistered USB: Force unbinding of drivers lacking reset_resume or other methods USB: ohci-pnx4008: I2C cleanups and fixes USB: debug port converter does not accept more than 8 byte packets ...
This commit is contained in:
commit
93ded9b8fd
@ -524,6 +524,44 @@ These utilities include endpoint autoconfiguration.
|
||||
<!-- !Edrivers/usb/gadget/epautoconf.c -->
|
||||
</sect1>
|
||||
|
||||
<sect1 id="composite"><title>Composite Device Framework</title>
|
||||
|
||||
<para>The core API is sufficient for writing drivers for composite
|
||||
USB devices (with more than one function in a given configuration),
|
||||
and also multi-configuration devices (also more than one function,
|
||||
but not necessarily sharing a given configuration).
|
||||
There is however an optional framework which makes it easier to
|
||||
reuse and combine functions.
|
||||
</para>
|
||||
|
||||
<para>Devices using this framework provide a <emphasis>struct
|
||||
usb_composite_driver</emphasis>, which in turn provides one or
|
||||
more <emphasis>struct usb_configuration</emphasis> instances.
|
||||
Each such configuration includes at least one
|
||||
<emphasis>struct usb_function</emphasis>, which packages a user
|
||||
visible role such as "network link" or "mass storage device".
|
||||
Management functions may also exist, such as "Device Firmware
|
||||
Upgrade".
|
||||
</para>
|
||||
|
||||
!Iinclude/linux/usb/composite.h
|
||||
!Edrivers/usb/gadget/composite.c
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="functions"><title>Composite Device Functions</title>
|
||||
|
||||
<para>At this writing, a few of the current gadget drivers have
|
||||
been converted to this framework.
|
||||
Near-term plans include converting all of them, except for "gadgetfs".
|
||||
</para>
|
||||
|
||||
!Edrivers/usb/gadget/f_acm.c
|
||||
!Edrivers/usb/gadget/f_serial.c
|
||||
|
||||
</sect1>
|
||||
|
||||
|
||||
</chapter>
|
||||
|
||||
<chapter id="controllers"><title>Peripheral Controller Drivers</title>
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
Linux Gadget Serial Driver v2.0
|
||||
11/20/2004
|
||||
(updated 8-May-2008 for v2.3)
|
||||
|
||||
|
||||
License and Disclaimer
|
||||
@ -31,7 +32,7 @@ Prerequisites
|
||||
-------------
|
||||
Versions of the gadget serial driver are available for the
|
||||
2.4 Linux kernels, but this document assumes you are using
|
||||
version 2.0 or later of the gadget serial driver in a 2.6
|
||||
version 2.3 or later of the gadget serial driver in a 2.6
|
||||
Linux kernel.
|
||||
|
||||
This document assumes that you are familiar with Linux and
|
||||
@ -40,6 +41,12 @@ standard utilities, use minicom and HyperTerminal, and work with
|
||||
USB and serial devices. It also assumes you configure the Linux
|
||||
gadget and usb drivers as modules.
|
||||
|
||||
With version 2.3 of the driver, major and minor device nodes are
|
||||
no longer statically defined. Your Linux based system should mount
|
||||
sysfs in /sys, and use "mdev" (in Busybox) or "udev" to make the
|
||||
/dev nodes matching the sysfs /sys/class/tty files.
|
||||
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
@ -104,15 +111,8 @@ driver. All this are listed under "USB Gadget Support" when
|
||||
configuring the kernel. Then rebuild and install the kernel or
|
||||
modules.
|
||||
|
||||
The gadget serial driver uses major number 127, for now. So you
|
||||
will need to create a device node for it, like this:
|
||||
|
||||
mknod /dev/ttygserial c 127 0
|
||||
|
||||
You only need to do this once.
|
||||
|
||||
Then you must load the gadget serial driver. To load it as an
|
||||
ACM device, do this:
|
||||
ACM device (recommended for interoperability), do this:
|
||||
|
||||
modprobe g_serial use_acm=1
|
||||
|
||||
@ -125,6 +125,23 @@ controller driver. This must be done each time you reboot the gadget
|
||||
side Linux system. You can add this to the start up scripts, if
|
||||
desired.
|
||||
|
||||
Your system should use mdev (from busybox) or udev to make the
|
||||
device nodes. After this gadget driver has been set up you should
|
||||
then see a /dev/ttyGS0 node:
|
||||
|
||||
# ls -l /dev/ttyGS0 | cat
|
||||
crw-rw---- 1 root root 253, 0 May 8 14:10 /dev/ttyGS0
|
||||
#
|
||||
|
||||
Note that the major number (253, above) is system-specific. If
|
||||
you need to create /dev nodes by hand, the right numbers to use
|
||||
will be in the /sys/class/tty/ttyGS0/dev file.
|
||||
|
||||
When you link this gadget driver early, perhaps even statically,
|
||||
you may want to set up an /etc/inittab entry to run "getty" on it.
|
||||
The /dev/ttyGS0 line should work like most any other serial port.
|
||||
|
||||
|
||||
If gadget serial is loaded as an ACM device you will want to use
|
||||
either the Windows or Linux ACM driver on the host side. If gadget
|
||||
serial is loaded as a bulk in/out device, you will want to use the
|
||||
|
@ -81,8 +81,11 @@ re-enumeration shows that the device now attached to that port has the
|
||||
same descriptors as before, including the Vendor and Product IDs, then
|
||||
the kernel continues to use the same device structure. In effect, the
|
||||
kernel treats the device as though it had merely been reset instead of
|
||||
unplugged. The same thing happens if the host controller is in the
|
||||
expected state but a USB device was unplugged and then replugged.
|
||||
unplugged.
|
||||
|
||||
The same thing happens if the host controller is in the expected state
|
||||
but a USB device was unplugged and then replugged, or if a USB device
|
||||
fails to carry out a normal resume.
|
||||
|
||||
If no device is now attached to the port, or if the descriptors are
|
||||
different from what the kernel remembers, then the treatment is what
|
||||
|
@ -1,165 +0,0 @@
|
||||
Specification and Internals for the New UHCI Driver (Whitepaper...)
|
||||
|
||||
brought to you by
|
||||
|
||||
Georg Acher, acher@in.tum.de (executive slave) (base guitar)
|
||||
Deti Fliegl, deti@fliegl.de (executive slave) (lead voice)
|
||||
Thomas Sailer, sailer@ife.ee.ethz.ch (chief consultant) (cheer leader)
|
||||
|
||||
$Id: README.uhci,v 1.1 1999/12/14 14:03:02 fliegl Exp $
|
||||
|
||||
This document and the new uhci sources can be found on
|
||||
http://hotswap.in.tum.de/usb
|
||||
|
||||
1. General issues
|
||||
|
||||
1.1 Why a new UHCI driver, we already have one?!?
|
||||
|
||||
Correct, but its internal structure got more and more mixed up by the (still
|
||||
ongoing) efforts to get isochronous transfers (ISO) to work.
|
||||
Since there is an increasing need for reliable ISO-transfers (especially
|
||||
for USB-audio needed by TS and for a DAB-USB-Receiver build by GA and DF),
|
||||
this state was a bit unsatisfying in our opinion, so we've decided (based
|
||||
on knowledge and experiences with the old UHCI driver) to start
|
||||
from scratch with a new approach, much simpler but at the same time more
|
||||
powerful.
|
||||
It is inspired by the way Win98/Win2000 handles USB requests via URBs,
|
||||
but it's definitely 100% free of MS-code and doesn't crash while
|
||||
unplugging an used ISO-device like Win98 ;-)
|
||||
Some code for HW setup and root hub management was taken from the
|
||||
original UHCI driver, but heavily modified to fit into the new code.
|
||||
The invention of the basic concept, and major coding were completed in two
|
||||
days (and nights) on the 16th and 17th of October 1999, now known as the
|
||||
great USB-October-Revolution started by GA, DF, and TS ;-)
|
||||
|
||||
Since the concept is in no way UHCI dependent, we hope that it will also be
|
||||
transferred to the OHCI-driver, so both drivers share a common API.
|
||||
|
||||
1.2. Advantages and disadvantages
|
||||
|
||||
+ All USB transfer types work now!
|
||||
+ Asynchronous operation
|
||||
+ Simple, but powerful interface (only two calls for start and cancel)
|
||||
+ Easy migration to the new API, simplified by a compatibility API
|
||||
+ Simple usage of ISO transfers
|
||||
+ Automatic linking of requests
|
||||
+ ISO transfers allow variable length for each frame and striping
|
||||
+ No CPU dependent and non-portable atomic memory access, no asm()-inlines
|
||||
+ Tested on x86 and Alpha
|
||||
|
||||
- Rewriting for ISO transfers needed
|
||||
|
||||
1.3. Is there some compatibility to the old API?
|
||||
|
||||
Yes, but only for control, bulk and interrupt transfers. We've implemented
|
||||
some wrapper calls for these transfer types. The usbcore works fine with
|
||||
these wrappers. For ISO there's no compatibility, because the old ISO-API
|
||||
and its semantics were unnecessary complicated in our opinion.
|
||||
|
||||
1.4. What's really working?
|
||||
|
||||
As said above, CTRL and BULK already work fine even with the wrappers,
|
||||
so legacy code wouldn't notice the change.
|
||||
Regarding to Thomas, ISO transfers now run stable with USB audio.
|
||||
INT transfers (e.g. mouse driver) work fine, too.
|
||||
|
||||
1.5. Are there any bugs?
|
||||
|
||||
No ;-)
|
||||
Hm...
|
||||
Well, of course this implementation needs extensive testing on all available
|
||||
hardware, but we believe that any fixes shouldn't harm the overall concept.
|
||||
|
||||
1.6. What should be done next?
|
||||
|
||||
A large part of the request handling seems to be identical for UHCI and
|
||||
OHCI, so it would be a good idea to extract the common parts and have only
|
||||
the HW specific stuff in uhci.c. Furthermore, all other USB device drivers
|
||||
should need URBification, if they use isochronous or interrupt transfers.
|
||||
One thing missing in the current implementation (and the old UHCI driver)
|
||||
is fair queueing for BULK transfers. Since this would need (in principle)
|
||||
the alteration of already constructed TD chains (to switch from depth to
|
||||
breadth execution), another way has to be found. Maybe some simple
|
||||
heuristics work with the same effect.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
2. Internal structure and mechanisms
|
||||
|
||||
To get quickly familiar with the internal structures, here's a short
|
||||
description how the new UHCI driver works. However, the ultimate source of
|
||||
truth is only uhci.c!
|
||||
|
||||
2.1. Descriptor structure (QHs and TDs)
|
||||
|
||||
During initialization, the following skeleton is allocated in init_skel:
|
||||
|
||||
framespecific | common chain
|
||||
|
||||
framelist[]
|
||||
[ 0 ]-----> TD --> TD -------\
|
||||
[ 1 ]-----> TD --> TD --------> TD ----> QH -------> QH -------> QH ---> NULL
|
||||
... TD --> TD -------/
|
||||
[1023]-----> TD --> TD ------/
|
||||
|
||||
^^ ^^ ^^ ^^ ^^ ^^
|
||||
1024 TDs for 7 TDs for 1 TD for Start of Start of End Chain
|
||||
ISO INT (2-128ms) 1ms-INT CTRL Chain BULK Chain
|
||||
|
||||
For each CTRL or BULK transfer a new QH is allocated and the containing data
|
||||
transfers are appended as (vertical) TDs. After building the whole QH with its
|
||||
dangling TDs, the QH is inserted before the BULK Chain QH (for CTRL) or
|
||||
before the End Chain QH (for BULK). Since only the QH->next pointers are
|
||||
affected, no atomic memory operation is required. The three QHs in the
|
||||
common chain are never equipped with TDs!
|
||||
|
||||
For ISO or INT, the TD for each frame is simply inserted into the appropriate
|
||||
ISO/INT-TD-chain for the desired frame. The 7 skeleton INT-TDs are scattered
|
||||
among the 1024 frames similar to the old UHCI driver.
|
||||
|
||||
For CTRL/BULK/ISO, the last TD in the transfer has the IOC-bit set. For INT,
|
||||
every TD (there is only one...) has the IOC-bit set.
|
||||
|
||||
Besides the data for the UHCI controller (2 or 4 32bit words), the descriptors
|
||||
are double-linked through the .vertical and .horizontal elements in the
|
||||
SW data of the descriptor (using the double-linked list structures and
|
||||
operations), but SW-linking occurs only in closed domains, i.e. for each of
|
||||
the 1024 ISO-chains and the 8 INT-chains there is a closed cycle. This
|
||||
simplifies all insertions and unlinking operations and avoids costly
|
||||
bus_to_virt()-calls.
|
||||
|
||||
2.2. URB structure and linking to QH/TDs
|
||||
|
||||
During assembly of the QH and TDs of the requested action, these descriptors
|
||||
are stored in urb->urb_list, so the allocated QH/TD descriptors are bound to
|
||||
this URB.
|
||||
If the assembly was successful and the descriptors were added to the HW chain,
|
||||
the corresponding URB is inserted into a global URB list for this controller.
|
||||
This list stores all pending URBs.
|
||||
|
||||
2.3. Interrupt processing
|
||||
|
||||
Since UHCI provides no means to directly detect completed transactions, the
|
||||
following is done in each UHCI interrupt (uhci_interrupt()):
|
||||
|
||||
For each URB in the pending queue (process_urb()), the ACTIVE-flag of the
|
||||
associated TDs are processed (depending on the transfer type
|
||||
process_{transfer|interrupt|iso}()). If the TDs are not active anymore,
|
||||
they indicate the completion of the transaction and the status is calculated.
|
||||
Inactive QH/TDs are removed from the HW chain (since the host controller
|
||||
already removed the TDs from the QH, no atomic access is needed) and
|
||||
eventually the URB is marked as completed (OK or errors) and removed from the
|
||||
pending queue. Then the next linked URB is submitted. After (or immediately
|
||||
before) that, the completion handler is called.
|
||||
|
||||
2.4. Unlinking URBs
|
||||
|
||||
First, all QH/TDs stored in the URB are unlinked from the HW chain.
|
||||
To ensure that the host controller really left a vertical TD chain, we
|
||||
wait for one frame. After that, the TDs are physically destroyed.
|
||||
|
||||
2.5. URB linking and the consequences
|
||||
|
||||
Since URBs can be linked and the corresponding submit_urb is called in
|
||||
the UHCI-interrupt, all work associated with URB/QH/TD assembly has to be
|
||||
interrupt save. This forces kmalloc to use GFP_ATOMIC in the interrupt.
|
@ -122,7 +122,7 @@ static void hid_reset(struct work_struct *work)
|
||||
dev_dbg(&usbhid->intf->dev, "resetting device\n");
|
||||
rc = rc_lock = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf);
|
||||
if (rc_lock >= 0) {
|
||||
rc = usb_reset_composite_device(hid_to_usb_dev(hid), usbhid->intf);
|
||||
rc = usb_reset_device(hid_to_usb_dev(hid));
|
||||
if (rc_lock)
|
||||
usb_unlock_device(hid_to_usb_dev(hid));
|
||||
}
|
||||
|
@ -1052,7 +1052,6 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance,
|
||||
|
||||
instance->usbatm = usbatm_instance;
|
||||
instance->modem_type = (struct cxacru_modem_type *) id->driver_info;
|
||||
memset(instance->card_info, 0, sizeof(instance->card_info));
|
||||
|
||||
mutex_init(&instance->poll_state_serialize);
|
||||
instance->poll_state = CXPOLL_STOPPED;
|
||||
|
@ -829,7 +829,6 @@ static int speedtch_bind(struct usbatm_data *usbatm,
|
||||
if (use_isoc) {
|
||||
const struct usb_host_interface *desc = data_intf->cur_altsetting;
|
||||
const __u8 target_address = USB_DIR_IN | usbatm->driver->isoc_in;
|
||||
int i;
|
||||
|
||||
use_isoc = 0; /* fall back to bulk if endpoint not found */
|
||||
|
||||
|
@ -159,12 +159,34 @@ static void acm_write_done(struct acm *acm, struct acm_wb *wb)
|
||||
spin_lock_irqsave(&acm->write_lock, flags);
|
||||
acm->write_ready = 1;
|
||||
wb->use = 0;
|
||||
acm->transmitting--;
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Poke write.
|
||||
*
|
||||
* the caller is responsible for locking
|
||||
*/
|
||||
|
||||
static int acm_start_wb(struct acm *acm, struct acm_wb *wb)
|
||||
{
|
||||
int rc;
|
||||
|
||||
acm->transmitting++;
|
||||
|
||||
wb->urb->transfer_buffer = wb->buf;
|
||||
wb->urb->transfer_dma = wb->dmah;
|
||||
wb->urb->transfer_buffer_length = wb->len;
|
||||
wb->urb->dev = acm->dev;
|
||||
|
||||
if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) {
|
||||
dbg("usb_submit_urb(write bulk) failed: %d", rc);
|
||||
acm_write_done(acm, wb);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int acm_write_start(struct acm *acm, int wbn)
|
||||
{
|
||||
unsigned long flags;
|
||||
@ -182,26 +204,31 @@ static int acm_write_start(struct acm *acm, int wbn)
|
||||
return 0; /* A white lie */
|
||||
}
|
||||
|
||||
wb = &acm->wb[wbn];
|
||||
if(acm_wb_is_avail(acm) <= 1)
|
||||
acm->write_ready = 0;
|
||||
|
||||
dbg("%s susp_count: %d", __func__, acm->susp_count);
|
||||
if (acm->susp_count) {
|
||||
acm->old_ready = acm->write_ready;
|
||||
acm->delayed_wb = wb;
|
||||
acm->write_ready = 0;
|
||||
schedule_work(&acm->waker);
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
return 0; /* A white lie */
|
||||
}
|
||||
usb_mark_last_busy(acm->dev);
|
||||
|
||||
if (!acm_wb_is_used(acm, wbn)) {
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
wb = &acm->wb[wbn];
|
||||
|
||||
if(acm_wb_is_avail(acm) <= 1)
|
||||
acm->write_ready = 0;
|
||||
rc = acm_start_wb(acm, wb);
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
|
||||
wb->urb->transfer_buffer = wb->buf;
|
||||
wb->urb->transfer_dma = wb->dmah;
|
||||
wb->urb->transfer_buffer_length = wb->len;
|
||||
wb->urb->dev = acm->dev;
|
||||
|
||||
if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) {
|
||||
dbg("usb_submit_urb(write bulk) failed: %d", rc);
|
||||
acm_write_done(acm, wb);
|
||||
}
|
||||
return rc;
|
||||
|
||||
}
|
||||
/*
|
||||
* attributes exported through sysfs
|
||||
@ -304,6 +331,7 @@ static void acm_ctrl_irq(struct urb *urb)
|
||||
break;
|
||||
}
|
||||
exit:
|
||||
usb_mark_last_busy(acm->dev);
|
||||
retval = usb_submit_urb (urb, GFP_ATOMIC);
|
||||
if (retval)
|
||||
err ("%s - usb_submit_urb failed with result %d",
|
||||
@ -320,8 +348,11 @@ static void acm_read_bulk(struct urb *urb)
|
||||
|
||||
dbg("Entering acm_read_bulk with status %d", status);
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
if (!ACM_READY(acm)) {
|
||||
dev_dbg(&acm->data->dev, "Aborting, acm not ready");
|
||||
return;
|
||||
}
|
||||
usb_mark_last_busy(acm->dev);
|
||||
|
||||
if (status)
|
||||
dev_dbg(&acm->data->dev, "bulk rx status %d\n", status);
|
||||
@ -331,6 +362,7 @@ static void acm_read_bulk(struct urb *urb)
|
||||
|
||||
if (likely(status == 0)) {
|
||||
spin_lock(&acm->read_lock);
|
||||
acm->processing++;
|
||||
list_add_tail(&rcv->list, &acm->spare_read_urbs);
|
||||
list_add_tail(&buf->list, &acm->filled_read_bufs);
|
||||
spin_unlock(&acm->read_lock);
|
||||
@ -343,7 +375,8 @@ static void acm_read_bulk(struct urb *urb)
|
||||
/* nevertheless the tasklet must be kicked unconditionally
|
||||
so the queue cannot dry up */
|
||||
}
|
||||
tasklet_schedule(&acm->urb_task);
|
||||
if (likely(!acm->susp_count))
|
||||
tasklet_schedule(&acm->urb_task);
|
||||
}
|
||||
|
||||
static void acm_rx_tasklet(unsigned long _acm)
|
||||
@ -354,16 +387,23 @@ static void acm_rx_tasklet(unsigned long _acm)
|
||||
struct acm_ru *rcv;
|
||||
unsigned long flags;
|
||||
unsigned char throttled;
|
||||
|
||||
dbg("Entering acm_rx_tasklet");
|
||||
|
||||
if (!ACM_READY(acm))
|
||||
{
|
||||
dbg("acm_rx_tasklet: ACM not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&acm->throttle_lock, flags);
|
||||
throttled = acm->throttle;
|
||||
spin_unlock_irqrestore(&acm->throttle_lock, flags);
|
||||
if (throttled)
|
||||
{
|
||||
dbg("acm_rx_tasklet: throttled");
|
||||
return;
|
||||
}
|
||||
|
||||
next_buffer:
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
@ -403,6 +443,7 @@ static void acm_rx_tasklet(unsigned long _acm)
|
||||
while (!list_empty(&acm->spare_read_bufs)) {
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
if (list_empty(&acm->spare_read_urbs)) {
|
||||
acm->processing = 0;
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
return;
|
||||
}
|
||||
@ -425,18 +466,23 @@ static void acm_rx_tasklet(unsigned long _acm)
|
||||
rcv->urb->transfer_dma = buf->dma;
|
||||
rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||
|
||||
dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf);
|
||||
|
||||
/* This shouldn't kill the driver as unsuccessful URBs are returned to the
|
||||
free-urbs-pool and resubmited ASAP */
|
||||
if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) {
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
if (acm->susp_count || usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) {
|
||||
list_add(&buf->list, &acm->spare_read_bufs);
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
list_add(&rcv->list, &acm->spare_read_urbs);
|
||||
acm->processing = 0;
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
return;
|
||||
} else {
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf);
|
||||
}
|
||||
}
|
||||
spin_lock_irqsave(&acm->read_lock, flags);
|
||||
acm->processing = 0;
|
||||
spin_unlock_irqrestore(&acm->read_lock, flags);
|
||||
}
|
||||
|
||||
/* data interface wrote those outgoing bytes */
|
||||
@ -463,6 +509,27 @@ static void acm_softint(struct work_struct *work)
|
||||
tty_wakeup(acm->tty);
|
||||
}
|
||||
|
||||
static void acm_waker(struct work_struct *waker)
|
||||
{
|
||||
struct acm *acm = container_of(waker, struct acm, waker);
|
||||
long flags;
|
||||
int rv;
|
||||
|
||||
rv = usb_autopm_get_interface(acm->control);
|
||||
if (rv < 0) {
|
||||
err("Autopm failure in %s", __func__);
|
||||
return;
|
||||
}
|
||||
if (acm->delayed_wb) {
|
||||
acm_start_wb(acm, acm->delayed_wb);
|
||||
acm->delayed_wb = NULL;
|
||||
}
|
||||
spin_lock_irqsave(&acm->write_lock, flags);
|
||||
acm->write_ready = acm->old_ready;
|
||||
spin_unlock_irqrestore(&acm->write_lock, flags);
|
||||
usb_autopm_put_interface(acm->control);
|
||||
}
|
||||
|
||||
/*
|
||||
* TTY handlers
|
||||
*/
|
||||
@ -492,6 +559,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
|
||||
|
||||
if (usb_autopm_get_interface(acm->control) < 0)
|
||||
goto early_bail;
|
||||
else
|
||||
acm->control->needs_remote_wakeup = 1;
|
||||
|
||||
mutex_lock(&acm->mutex);
|
||||
if (acm->used++) {
|
||||
@ -509,6 +578,7 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
|
||||
if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) &&
|
||||
(acm->ctrl_caps & USB_CDC_CAP_LINE))
|
||||
goto full_bailout;
|
||||
usb_autopm_put_interface(acm->control);
|
||||
|
||||
INIT_LIST_HEAD(&acm->spare_read_urbs);
|
||||
INIT_LIST_HEAD(&acm->spare_read_bufs);
|
||||
@ -570,12 +640,14 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
|
||||
mutex_lock(&open_mutex);
|
||||
if (!--acm->used) {
|
||||
if (acm->dev) {
|
||||
usb_autopm_get_interface(acm->control);
|
||||
acm_set_control(acm, acm->ctrlout = 0);
|
||||
usb_kill_urb(acm->ctrlurb);
|
||||
for (i = 0; i < ACM_NW; i++)
|
||||
usb_kill_urb(acm->wb[i].urb);
|
||||
for (i = 0; i < nr; i++)
|
||||
usb_kill_urb(acm->ru[i].urb);
|
||||
acm->control->needs_remote_wakeup = 0;
|
||||
usb_autopm_put_interface(acm->control);
|
||||
} else
|
||||
acm_tty_unregister(acm);
|
||||
@ -766,7 +838,7 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios
|
||||
* USB probe and disconnect routines.
|
||||
*/
|
||||
|
||||
/* Little helper: write buffers free */
|
||||
/* Little helpers: write/read buffers free */
|
||||
static void acm_write_buffers_free(struct acm *acm)
|
||||
{
|
||||
int i;
|
||||
@ -777,6 +849,15 @@ static void acm_write_buffers_free(struct acm *acm)
|
||||
}
|
||||
}
|
||||
|
||||
static void acm_read_buffers_free(struct acm *acm)
|
||||
{
|
||||
struct usb_device *usb_dev = interface_to_usbdev(acm->control);
|
||||
int i, n = acm->rx_buflimit;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
|
||||
}
|
||||
|
||||
/* Little helper: write buffers allocate */
|
||||
static int acm_write_buffers_alloc(struct acm *acm)
|
||||
{
|
||||
@ -987,6 +1068,7 @@ static int acm_probe (struct usb_interface *intf,
|
||||
acm->urb_task.func = acm_rx_tasklet;
|
||||
acm->urb_task.data = (unsigned long) acm;
|
||||
INIT_WORK(&acm->work, acm_softint);
|
||||
INIT_WORK(&acm->waker, acm_waker);
|
||||
spin_lock_init(&acm->throttle_lock);
|
||||
spin_lock_init(&acm->write_lock);
|
||||
spin_lock_init(&acm->read_lock);
|
||||
@ -1098,8 +1180,7 @@ static int acm_probe (struct usb_interface *intf,
|
||||
for (i = 0; i < ACM_NW; i++)
|
||||
usb_free_urb(acm->wb[i].urb);
|
||||
alloc_fail7:
|
||||
for (i = 0; i < num_rx_buf; i++)
|
||||
usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
|
||||
acm_read_buffers_free(acm);
|
||||
for (i = 0; i < num_rx_buf; i++)
|
||||
usb_free_urb(acm->ru[i].urb);
|
||||
usb_free_urb(acm->ctrlurb);
|
||||
@ -1116,6 +1197,7 @@ static int acm_probe (struct usb_interface *intf,
|
||||
static void stop_data_traffic(struct acm *acm)
|
||||
{
|
||||
int i;
|
||||
dbg("Entering stop_data_traffic");
|
||||
|
||||
tasklet_disable(&acm->urb_task);
|
||||
|
||||
@ -1128,21 +1210,16 @@ static void stop_data_traffic(struct acm *acm)
|
||||
tasklet_enable(&acm->urb_task);
|
||||
|
||||
cancel_work_sync(&acm->work);
|
||||
cancel_work_sync(&acm->waker);
|
||||
}
|
||||
|
||||
static void acm_disconnect(struct usb_interface *intf)
|
||||
{
|
||||
struct acm *acm = usb_get_intfdata(intf);
|
||||
struct usb_device *usb_dev = interface_to_usbdev(intf);
|
||||
int i;
|
||||
|
||||
if (!acm || !acm->dev) {
|
||||
dbg("disconnect on nonexisting interface");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&open_mutex);
|
||||
if (!usb_get_intfdata(intf)) {
|
||||
if (!acm || !acm->dev) {
|
||||
mutex_unlock(&open_mutex);
|
||||
return;
|
||||
}
|
||||
@ -1161,10 +1238,10 @@ static void acm_disconnect(struct usb_interface *intf)
|
||||
|
||||
acm_write_buffers_free(acm);
|
||||
usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
|
||||
for (i = 0; i < acm->rx_buflimit; i++)
|
||||
usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
|
||||
acm_read_buffers_free(acm);
|
||||
|
||||
usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : intf);
|
||||
usb_driver_release_interface(&acm_driver, intf == acm->control ?
|
||||
acm->data : acm->control);
|
||||
|
||||
if (!acm->used) {
|
||||
acm_tty_unregister(acm);
|
||||
@ -1178,11 +1255,31 @@ static void acm_disconnect(struct usb_interface *intf)
|
||||
tty_hangup(acm->tty);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int acm_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
{
|
||||
struct acm *acm = usb_get_intfdata(intf);
|
||||
int cnt;
|
||||
|
||||
if (acm->susp_count++)
|
||||
if (acm->dev->auto_pm) {
|
||||
int b;
|
||||
|
||||
spin_lock_irq(&acm->read_lock);
|
||||
spin_lock(&acm->write_lock);
|
||||
b = acm->processing + acm->transmitting;
|
||||
spin_unlock(&acm->write_lock);
|
||||
spin_unlock_irq(&acm->read_lock);
|
||||
if (b)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
spin_lock_irq(&acm->read_lock);
|
||||
spin_lock(&acm->write_lock);
|
||||
cnt = acm->susp_count++;
|
||||
spin_unlock(&acm->write_lock);
|
||||
spin_unlock_irq(&acm->read_lock);
|
||||
|
||||
if (cnt)
|
||||
return 0;
|
||||
/*
|
||||
we treat opened interfaces differently,
|
||||
@ -1201,15 +1298,21 @@ static int acm_resume(struct usb_interface *intf)
|
||||
{
|
||||
struct acm *acm = usb_get_intfdata(intf);
|
||||
int rv = 0;
|
||||
int cnt;
|
||||
|
||||
if (--acm->susp_count)
|
||||
spin_lock_irq(&acm->read_lock);
|
||||
acm->susp_count -= 1;
|
||||
cnt = acm->susp_count;
|
||||
spin_unlock_irq(&acm->read_lock);
|
||||
|
||||
if (cnt)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&acm->mutex);
|
||||
if (acm->used) {
|
||||
rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
|
||||
if (rv < 0)
|
||||
goto err_out;
|
||||
goto err_out;
|
||||
|
||||
tasklet_schedule(&acm->urb_task);
|
||||
}
|
||||
@ -1218,6 +1321,8 @@ static int acm_resume(struct usb_interface *intf)
|
||||
mutex_unlock(&acm->mutex);
|
||||
return rv;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
/*
|
||||
* USB driver structure.
|
||||
*/
|
||||
@ -1273,10 +1378,14 @@ static struct usb_driver acm_driver = {
|
||||
.name = "cdc_acm",
|
||||
.probe = acm_probe,
|
||||
.disconnect = acm_disconnect,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = acm_suspend,
|
||||
.resume = acm_resume,
|
||||
#endif
|
||||
.id_table = acm_ids,
|
||||
#ifdef CONFIG_PM
|
||||
.supports_autosuspend = 1,
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -107,10 +107,14 @@ struct acm {
|
||||
struct list_head filled_read_bufs;
|
||||
int write_used; /* number of non-empty write buffers */
|
||||
int write_ready; /* write urb is not running */
|
||||
int old_ready;
|
||||
int processing;
|
||||
int transmitting;
|
||||
spinlock_t write_lock;
|
||||
struct mutex mutex;
|
||||
struct usb_cdc_line_coding line; /* bits, stop, parity */
|
||||
struct work_struct work; /* work queue entry for line discipline waking up */
|
||||
struct work_struct waker;
|
||||
struct tasklet_struct urb_task; /* rx processing */
|
||||
spinlock_t throttle_lock; /* synchronize throtteling and read callback */
|
||||
unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
|
||||
@ -123,6 +127,7 @@ struct acm {
|
||||
unsigned char clocal; /* termios CLOCAL */
|
||||
unsigned int ctrl_caps; /* control capabilities from the class specific header */
|
||||
unsigned int susp_count; /* number of suspended interfaces */
|
||||
struct acm_wb *delayed_wb; /* write queued for a device about to be woken */
|
||||
};
|
||||
|
||||
#define CDC_DATA_INTERFACE_TYPE 0x0a
|
||||
|
@ -28,8 +28,9 @@
|
||||
/*
|
||||
* Version Information
|
||||
*/
|
||||
#define DRIVER_VERSION "v0.02"
|
||||
#define DRIVER_VERSION "v0.03"
|
||||
#define DRIVER_AUTHOR "Oliver Neukum"
|
||||
#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
|
||||
|
||||
static struct usb_device_id wdm_ids[] = {
|
||||
{
|
||||
@ -87,6 +88,7 @@ struct wdm_device {
|
||||
dma_addr_t ihandle;
|
||||
struct mutex wlock;
|
||||
struct mutex rlock;
|
||||
struct mutex plock;
|
||||
wait_queue_head_t wait;
|
||||
struct work_struct rxwork;
|
||||
int werr;
|
||||
@ -205,7 +207,7 @@ static void wdm_int_callback(struct urb *urb)
|
||||
req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
|
||||
req->wValue = 0;
|
||||
req->wIndex = desc->inum;
|
||||
req->wLength = cpu_to_le16(desc->bMaxPacketSize0);
|
||||
req->wLength = cpu_to_le16(desc->wMaxCommand);
|
||||
|
||||
usb_fill_control_urb(
|
||||
desc->response,
|
||||
@ -214,7 +216,7 @@ static void wdm_int_callback(struct urb *urb)
|
||||
usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
|
||||
(unsigned char *)req,
|
||||
desc->inbuf,
|
||||
desc->bMaxPacketSize0,
|
||||
desc->wMaxCommand,
|
||||
wdm_in_callback,
|
||||
desc
|
||||
);
|
||||
@ -247,6 +249,7 @@ static void wdm_int_callback(struct urb *urb)
|
||||
|
||||
static void kill_urbs(struct wdm_device *desc)
|
||||
{
|
||||
/* the order here is essential */
|
||||
usb_kill_urb(desc->command);
|
||||
usb_kill_urb(desc->validity);
|
||||
usb_kill_urb(desc->response);
|
||||
@ -266,7 +269,7 @@ static void cleanup(struct wdm_device *desc)
|
||||
desc->sbuf,
|
||||
desc->validity->transfer_dma);
|
||||
usb_buffer_free(interface_to_usbdev(desc->intf),
|
||||
desc->wMaxPacketSize,
|
||||
desc->wMaxCommand,
|
||||
desc->inbuf,
|
||||
desc->response->transfer_dma);
|
||||
kfree(desc->orq);
|
||||
@ -299,6 +302,9 @@ static ssize_t wdm_write
|
||||
if (r)
|
||||
goto outnl;
|
||||
|
||||
r = usb_autopm_get_interface(desc->intf);
|
||||
if (r < 0)
|
||||
goto outnp;
|
||||
r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
|
||||
&desc->flags));
|
||||
if (r < 0)
|
||||
@ -347,11 +353,14 @@ static ssize_t wdm_write
|
||||
if (rv < 0) {
|
||||
kfree(buf);
|
||||
clear_bit(WDM_IN_USE, &desc->flags);
|
||||
err("Tx URB error: %d", rv);
|
||||
} else {
|
||||
dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
|
||||
req->wIndex);
|
||||
}
|
||||
out:
|
||||
usb_autopm_put_interface(desc->intf);
|
||||
outnp:
|
||||
mutex_unlock(&desc->wlock);
|
||||
outnl:
|
||||
return rv < 0 ? rv : count;
|
||||
@ -376,6 +385,11 @@ static ssize_t wdm_read
|
||||
rv = wait_event_interruptible(desc->wait,
|
||||
test_bit(WDM_READ, &desc->flags));
|
||||
|
||||
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
||||
rv = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
usb_mark_last_busy(interface_to_usbdev(desc->intf));
|
||||
if (rv < 0) {
|
||||
rv = -ERESTARTSYS;
|
||||
goto err;
|
||||
@ -418,6 +432,9 @@ static ssize_t wdm_read
|
||||
desc->ubuf[i] = desc->ubuf[i + cntr];
|
||||
|
||||
desc->length -= cntr;
|
||||
/* in case we had outstanding data */
|
||||
if (!desc->length)
|
||||
clear_bit(WDM_READ, &desc->flags);
|
||||
rv = cntr;
|
||||
|
||||
err:
|
||||
@ -480,18 +497,28 @@ static int wdm_open(struct inode *inode, struct file *file)
|
||||
if (test_bit(WDM_DISCONNECTING, &desc->flags))
|
||||
goto out;
|
||||
|
||||
desc->count++;
|
||||
;
|
||||
file->private_data = desc;
|
||||
|
||||
rv = usb_submit_urb(desc->validity, GFP_KERNEL);
|
||||
|
||||
rv = usb_autopm_get_interface(desc->intf);
|
||||
if (rv < 0) {
|
||||
desc->count--;
|
||||
err("Error submitting int urb - %d", rv);
|
||||
err("Error autopm - %d", rv);
|
||||
goto out;
|
||||
}
|
||||
rv = 0;
|
||||
intf->needs_remote_wakeup = 1;
|
||||
|
||||
mutex_lock(&desc->plock);
|
||||
if (!desc->count++) {
|
||||
rv = usb_submit_urb(desc->validity, GFP_KERNEL);
|
||||
if (rv < 0) {
|
||||
desc->count--;
|
||||
err("Error submitting int urb - %d", rv);
|
||||
}
|
||||
} else {
|
||||
rv = 0;
|
||||
}
|
||||
mutex_unlock(&desc->plock);
|
||||
usb_autopm_put_interface(desc->intf);
|
||||
out:
|
||||
mutex_unlock(&wdm_mutex);
|
||||
return rv;
|
||||
@ -502,10 +529,15 @@ static int wdm_release(struct inode *inode, struct file *file)
|
||||
struct wdm_device *desc = file->private_data;
|
||||
|
||||
mutex_lock(&wdm_mutex);
|
||||
mutex_lock(&desc->plock);
|
||||
desc->count--;
|
||||
mutex_unlock(&desc->plock);
|
||||
|
||||
if (!desc->count) {
|
||||
dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
|
||||
kill_urbs(desc);
|
||||
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
|
||||
desc->intf->needs_remote_wakeup = 0;
|
||||
}
|
||||
mutex_unlock(&wdm_mutex);
|
||||
return 0;
|
||||
@ -597,6 +629,7 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
||||
goto out;
|
||||
mutex_init(&desc->wlock);
|
||||
mutex_init(&desc->rlock);
|
||||
mutex_init(&desc->plock);
|
||||
spin_lock_init(&desc->iuspin);
|
||||
init_waitqueue_head(&desc->wait);
|
||||
desc->wMaxCommand = maxcom;
|
||||
@ -698,6 +731,7 @@ static void wdm_disconnect(struct usb_interface *intf)
|
||||
spin_lock_irqsave(&desc->iuspin, flags);
|
||||
set_bit(WDM_DISCONNECTING, &desc->flags);
|
||||
set_bit(WDM_READ, &desc->flags);
|
||||
/* to terminate pending flushes */
|
||||
clear_bit(WDM_IN_USE, &desc->flags);
|
||||
spin_unlock_irqrestore(&desc->iuspin, flags);
|
||||
cancel_work_sync(&desc->rxwork);
|
||||
@ -708,11 +742,81 @@ static void wdm_disconnect(struct usb_interface *intf)
|
||||
mutex_unlock(&wdm_mutex);
|
||||
}
|
||||
|
||||
static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
{
|
||||
struct wdm_device *desc = usb_get_intfdata(intf);
|
||||
int rv = 0;
|
||||
|
||||
dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
|
||||
|
||||
mutex_lock(&desc->plock);
|
||||
#ifdef CONFIG_PM
|
||||
if (interface_to_usbdev(desc->intf)->auto_pm && test_bit(WDM_IN_USE, &desc->flags)) {
|
||||
rv = -EBUSY;
|
||||
} else {
|
||||
#endif
|
||||
cancel_work_sync(&desc->rxwork);
|
||||
kill_urbs(desc);
|
||||
#ifdef CONFIG_PM
|
||||
}
|
||||
#endif
|
||||
mutex_unlock(&desc->plock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int recover_from_urb_loss(struct wdm_device *desc)
|
||||
{
|
||||
int rv = 0;
|
||||
|
||||
if (desc->count) {
|
||||
rv = usb_submit_urb(desc->validity, GFP_NOIO);
|
||||
if (rv < 0)
|
||||
err("Error resume submitting int urb - %d", rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
static int wdm_resume(struct usb_interface *intf)
|
||||
{
|
||||
struct wdm_device *desc = usb_get_intfdata(intf);
|
||||
int rv;
|
||||
|
||||
dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
|
||||
mutex_lock(&desc->plock);
|
||||
rv = recover_from_urb_loss(desc);
|
||||
mutex_unlock(&desc->plock);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int wdm_pre_reset(struct usb_interface *intf)
|
||||
{
|
||||
struct wdm_device *desc = usb_get_intfdata(intf);
|
||||
|
||||
mutex_lock(&desc->plock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wdm_post_reset(struct usb_interface *intf)
|
||||
{
|
||||
struct wdm_device *desc = usb_get_intfdata(intf);
|
||||
int rv;
|
||||
|
||||
rv = recover_from_urb_loss(desc);
|
||||
mutex_unlock(&desc->plock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct usb_driver wdm_driver = {
|
||||
.name = "cdc_wdm",
|
||||
.probe = wdm_probe,
|
||||
.disconnect = wdm_disconnect,
|
||||
.suspend = wdm_suspend,
|
||||
.resume = wdm_resume,
|
||||
.reset_resume = wdm_resume,
|
||||
.pre_reset = wdm_pre_reset,
|
||||
.post_reset = wdm_post_reset,
|
||||
.id_table = wdm_ids,
|
||||
.supports_autosuspend = 1,
|
||||
};
|
||||
|
||||
/* --- low level module stuff --- */
|
||||
@ -735,6 +839,5 @@ module_init(wdm_init);
|
||||
module_exit(wdm_exit);
|
||||
|
||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION("USB Abstract Control Model driver for "
|
||||
"USB WCM Device Management");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -46,8 +46,6 @@
|
||||
* 2000-07-05: Ashley Montanaro <ashley@compsoc.man.ac.uk>
|
||||
* Converted file reading routine to dump to buffer once
|
||||
* per device, not per bus
|
||||
*
|
||||
* $Id: devices.c,v 1.5 2000/01/11 13:58:21 tom Exp $
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
@ -63,8 +61,6 @@
|
||||
#include "usb.h"
|
||||
#include "hcd.h"
|
||||
|
||||
#define MAX_TOPO_LEVEL 6
|
||||
|
||||
/* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */
|
||||
#define ALLOW_SERIAL_NUMBER
|
||||
|
||||
|
@ -19,8 +19,6 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id: devio.c,v 1.7 2000/02/01 17:28:48 fliegl Exp $
|
||||
*
|
||||
* This file implements the usbfs/x/y files, where
|
||||
* x is the bus number and y the device number.
|
||||
*
|
||||
@ -61,6 +59,22 @@
|
||||
/* Mutual exclusion for removal, open, and release */
|
||||
DEFINE_MUTEX(usbfs_mutex);
|
||||
|
||||
struct dev_state {
|
||||
struct list_head list; /* state list */
|
||||
struct usb_device *dev;
|
||||
struct file *file;
|
||||
spinlock_t lock; /* protects the async urb lists */
|
||||
struct list_head async_pending;
|
||||
struct list_head async_completed;
|
||||
wait_queue_head_t wait; /* wake up if a request completed */
|
||||
unsigned int discsignr;
|
||||
struct pid *disc_pid;
|
||||
uid_t disc_uid, disc_euid;
|
||||
void __user *disccontext;
|
||||
unsigned long ifclaimed;
|
||||
u32 secid;
|
||||
};
|
||||
|
||||
struct async {
|
||||
struct list_head asynclist;
|
||||
struct dev_state *ps;
|
||||
@ -536,23 +550,19 @@ static int check_ctrlrecip(struct dev_state *ps, unsigned int requesttype,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __match_minor(struct device *dev, void *data)
|
||||
static int match_devt(struct device *dev, void *data)
|
||||
{
|
||||
int minor = *((int *)data);
|
||||
|
||||
if (dev->devt == MKDEV(USB_DEVICE_MAJOR, minor))
|
||||
return 1;
|
||||
return 0;
|
||||
return dev->devt == (dev_t) (unsigned long) data;
|
||||
}
|
||||
|
||||
static struct usb_device *usbdev_lookup_by_minor(int minor)
|
||||
static struct usb_device *usbdev_lookup_by_devt(dev_t devt)
|
||||
{
|
||||
struct device *dev;
|
||||
|
||||
dev = bus_find_device(&usb_bus_type, NULL, &minor, __match_minor);
|
||||
dev = bus_find_device(&usb_bus_type, NULL,
|
||||
(void *) (unsigned long) devt, match_devt);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
put_device(dev);
|
||||
return container_of(dev, struct usb_device, dev);
|
||||
}
|
||||
|
||||
@ -575,21 +585,27 @@ static int usbdev_open(struct inode *inode, struct file *file)
|
||||
goto out;
|
||||
|
||||
ret = -ENOENT;
|
||||
|
||||
/* usbdev device-node */
|
||||
if (imajor(inode) == USB_DEVICE_MAJOR)
|
||||
dev = usbdev_lookup_by_minor(iminor(inode));
|
||||
dev = usbdev_lookup_by_devt(inode->i_rdev);
|
||||
#ifdef CONFIG_USB_DEVICEFS
|
||||
/* procfs file */
|
||||
if (!dev)
|
||||
if (!dev) {
|
||||
dev = inode->i_private;
|
||||
if (dev && dev->usbfs_dentry &&
|
||||
dev->usbfs_dentry->d_inode == inode)
|
||||
usb_get_dev(dev);
|
||||
else
|
||||
dev = NULL;
|
||||
}
|
||||
#endif
|
||||
if (!dev)
|
||||
if (!dev || dev->state == USB_STATE_NOTATTACHED)
|
||||
goto out;
|
||||
ret = usb_autoresume_device(dev);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
usb_get_dev(dev);
|
||||
ret = 0;
|
||||
ps->dev = dev;
|
||||
ps->file = file;
|
||||
@ -609,8 +625,10 @@ static int usbdev_open(struct inode *inode, struct file *file)
|
||||
list_add_tail(&ps->list, &dev->filelist);
|
||||
file->private_data = ps;
|
||||
out:
|
||||
if (ret)
|
||||
if (ret) {
|
||||
kfree(ps);
|
||||
usb_put_dev(dev);
|
||||
}
|
||||
mutex_unlock(&usbfs_mutex);
|
||||
unlock_kernel();
|
||||
return ret;
|
||||
@ -874,7 +892,7 @@ static int proc_connectinfo(struct dev_state *ps, void __user *arg)
|
||||
|
||||
static int proc_resetdevice(struct dev_state *ps)
|
||||
{
|
||||
return usb_reset_composite_device(ps->dev, NULL);
|
||||
return usb_reset_device(ps->dev);
|
||||
}
|
||||
|
||||
static int proc_setintf(struct dev_state *ps, void __user *arg)
|
||||
@ -1682,25 +1700,49 @@ const struct file_operations usbdev_file_operations = {
|
||||
.release = usbdev_release,
|
||||
};
|
||||
|
||||
void usb_fs_classdev_common_remove(struct usb_device *udev)
|
||||
{
|
||||
struct dev_state *ps;
|
||||
struct siginfo sinfo;
|
||||
|
||||
while (!list_empty(&udev->filelist)) {
|
||||
ps = list_entry(udev->filelist.next, struct dev_state, list);
|
||||
destroy_all_async(ps);
|
||||
wake_up_all(&ps->wait);
|
||||
list_del_init(&ps->list);
|
||||
if (ps->discsignr) {
|
||||
sinfo.si_signo = ps->discsignr;
|
||||
sinfo.si_errno = EPIPE;
|
||||
sinfo.si_code = SI_ASYNCIO;
|
||||
sinfo.si_addr = ps->disccontext;
|
||||
kill_pid_info_as_uid(ps->discsignr, &sinfo,
|
||||
ps->disc_pid, ps->disc_uid,
|
||||
ps->disc_euid, ps->secid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USB_DEVICE_CLASS
|
||||
static struct class *usb_classdev_class;
|
||||
|
||||
static int usb_classdev_add(struct usb_device *dev)
|
||||
{
|
||||
int minor = ((dev->bus->busnum-1) * 128) + (dev->devnum-1);
|
||||
|
||||
dev->usb_classdev = device_create(usb_classdev_class, &dev->dev,
|
||||
MKDEV(USB_DEVICE_MAJOR, minor),
|
||||
"usbdev%d.%d", dev->bus->busnum, dev->devnum);
|
||||
if (IS_ERR(dev->usb_classdev))
|
||||
return PTR_ERR(dev->usb_classdev);
|
||||
struct device *cldev;
|
||||
|
||||
cldev = device_create(usb_classdev_class, &dev->dev, dev->dev.devt,
|
||||
"usbdev%d.%d", dev->bus->busnum,
|
||||
dev->devnum);
|
||||
if (IS_ERR(cldev))
|
||||
return PTR_ERR(cldev);
|
||||
dev->usb_classdev = cldev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usb_classdev_remove(struct usb_device *dev)
|
||||
{
|
||||
device_unregister(dev->usb_classdev);
|
||||
if (dev->usb_classdev)
|
||||
device_unregister(dev->usb_classdev);
|
||||
usb_fs_classdev_common_remove(dev);
|
||||
}
|
||||
|
||||
static int usb_classdev_notify(struct notifier_block *self,
|
||||
|
@ -201,6 +201,7 @@ static int usb_probe_interface(struct device *dev)
|
||||
|
||||
intf = to_usb_interface(dev);
|
||||
udev = interface_to_usbdev(intf);
|
||||
intf->needs_binding = 0;
|
||||
|
||||
if (udev->authorized == 0) {
|
||||
dev_err(&intf->dev, "Device is not authorized for usage\n");
|
||||
@ -257,15 +258,16 @@ static int usb_unbind_interface(struct device *dev)
|
||||
udev = interface_to_usbdev(intf);
|
||||
error = usb_autoresume_device(udev);
|
||||
|
||||
/* release all urbs for this interface */
|
||||
usb_disable_interface(interface_to_usbdev(intf), intf);
|
||||
/* Terminate all URBs for this interface unless the driver
|
||||
* supports "soft" unbinding.
|
||||
*/
|
||||
if (!driver->soft_unbind)
|
||||
usb_disable_interface(udev, intf);
|
||||
|
||||
driver->disconnect(intf);
|
||||
|
||||
/* reset other interface state */
|
||||
usb_set_interface(interface_to_usbdev(intf),
|
||||
intf->altsetting[0].desc.bInterfaceNumber,
|
||||
0);
|
||||
usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0);
|
||||
usb_set_intfdata(intf, NULL);
|
||||
|
||||
intf->condition = USB_INTERFACE_UNBOUND;
|
||||
@ -310,6 +312,7 @@ int usb_driver_claim_interface(struct usb_driver *driver,
|
||||
|
||||
dev->driver = &driver->drvwrap.driver;
|
||||
usb_set_intfdata(iface, priv);
|
||||
iface->needs_binding = 0;
|
||||
|
||||
usb_pm_lock(udev);
|
||||
iface->condition = USB_INTERFACE_BOUND;
|
||||
@ -586,7 +589,7 @@ static int usb_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
struct usb_device *usb_dev;
|
||||
|
||||
/* driver is often null here; dev_dbg() would oops */
|
||||
pr_debug("usb %s: uevent\n", dev->bus_id);
|
||||
pr_debug("usb %s: uevent\n", dev_name(dev));
|
||||
|
||||
if (is_usb_device(dev))
|
||||
usb_dev = to_usb_device(dev);
|
||||
@ -596,11 +599,11 @@ static int usb_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
}
|
||||
|
||||
if (usb_dev->devnum < 0) {
|
||||
pr_debug("usb %s: already deleted?\n", dev->bus_id);
|
||||
pr_debug("usb %s: already deleted?\n", dev_name(dev));
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!usb_dev->bus) {
|
||||
pr_debug("usb %s: bus removed?\n", dev->bus_id);
|
||||
pr_debug("usb %s: bus removed?\n", dev_name(dev));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -771,6 +774,104 @@ void usb_deregister(struct usb_driver *driver)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_deregister);
|
||||
|
||||
|
||||
/* Forced unbinding of a USB interface driver, either because
|
||||
* it doesn't support pre_reset/post_reset/reset_resume or
|
||||
* because it doesn't support suspend/resume.
|
||||
*
|
||||
* The caller must hold @intf's device's lock, but not its pm_mutex
|
||||
* and not @intf->dev.sem.
|
||||
*/
|
||||
void usb_forced_unbind_intf(struct usb_interface *intf)
|
||||
{
|
||||
struct usb_driver *driver = to_usb_driver(intf->dev.driver);
|
||||
|
||||
dev_dbg(&intf->dev, "forced unbind\n");
|
||||
usb_driver_release_interface(driver, intf);
|
||||
|
||||
/* Mark the interface for later rebinding */
|
||||
intf->needs_binding = 1;
|
||||
}
|
||||
|
||||
/* Delayed forced unbinding of a USB interface driver and scan
|
||||
* for rebinding.
|
||||
*
|
||||
* The caller must hold @intf's device's lock, but not its pm_mutex
|
||||
* and not @intf->dev.sem.
|
||||
*
|
||||
* FIXME: The caller must block system sleep transitions.
|
||||
*/
|
||||
void usb_rebind_intf(struct usb_interface *intf)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Delayed unbind of an existing driver */
|
||||
if (intf->dev.driver) {
|
||||
struct usb_driver *driver =
|
||||
to_usb_driver(intf->dev.driver);
|
||||
|
||||
dev_dbg(&intf->dev, "forced unbind\n");
|
||||
usb_driver_release_interface(driver, intf);
|
||||
}
|
||||
|
||||
/* Try to rebind the interface */
|
||||
intf->needs_binding = 0;
|
||||
rc = device_attach(&intf->dev);
|
||||
if (rc < 0)
|
||||
dev_warn(&intf->dev, "rebind failed: %d\n", rc);
|
||||
}
|
||||
|
||||
#define DO_UNBIND 0
|
||||
#define DO_REBIND 1
|
||||
|
||||
/* Unbind drivers for @udev's interfaces that don't support suspend/resume,
|
||||
* or rebind interfaces that have been unbound, according to @action.
|
||||
*
|
||||
* The caller must hold @udev's device lock.
|
||||
* FIXME: For rebinds, the caller must block system sleep transitions.
|
||||
*/
|
||||
static void do_unbind_rebind(struct usb_device *udev, int action)
|
||||
{
|
||||
struct usb_host_config *config;
|
||||
int i;
|
||||
struct usb_interface *intf;
|
||||
struct usb_driver *drv;
|
||||
|
||||
config = udev->actconfig;
|
||||
if (config) {
|
||||
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
|
||||
intf = config->interface[i];
|
||||
switch (action) {
|
||||
case DO_UNBIND:
|
||||
if (intf->dev.driver) {
|
||||
drv = to_usb_driver(intf->dev.driver);
|
||||
if (!drv->suspend || !drv->resume)
|
||||
usb_forced_unbind_intf(intf);
|
||||
}
|
||||
break;
|
||||
case DO_REBIND:
|
||||
if (intf->needs_binding) {
|
||||
|
||||
/* FIXME: The next line is needed because we are going to probe
|
||||
* the interface, but as far as the PM core is concerned the
|
||||
* interface is still suspended. The problem wouldn't exist
|
||||
* if we could rebind the interface during the interface's own
|
||||
* resume() call, but at the time the usb_device isn't locked!
|
||||
*
|
||||
* The real solution will be to carry this out during the device's
|
||||
* complete() callback. Until that is implemented, we have to
|
||||
* use this hack.
|
||||
*/
|
||||
// intf->dev.power.sleeping = 0;
|
||||
|
||||
usb_rebind_intf(intf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* Caller has locked udev's pm_mutex */
|
||||
@ -805,8 +906,6 @@ static int usb_resume_device(struct usb_device *udev)
|
||||
|
||||
if (udev->state == USB_STATE_NOTATTACHED)
|
||||
goto done;
|
||||
if (udev->state != USB_STATE_SUSPENDED && !udev->reset_resume)
|
||||
goto done;
|
||||
|
||||
/* Can't resume it if it doesn't have a driver. */
|
||||
if (udev->dev.driver == NULL) {
|
||||
@ -842,7 +941,7 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg)
|
||||
goto done;
|
||||
driver = to_usb_driver(intf->dev.driver);
|
||||
|
||||
if (driver->suspend && driver->resume) {
|
||||
if (driver->suspend) {
|
||||
status = driver->suspend(intf, msg);
|
||||
if (status == 0)
|
||||
mark_quiesced(intf);
|
||||
@ -850,12 +949,10 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg)
|
||||
dev_err(&intf->dev, "%s error %d\n",
|
||||
"suspend", status);
|
||||
} else {
|
||||
/*
|
||||
* FIXME else if there's no suspend method, disconnect...
|
||||
* Not possible if auto_pm is set...
|
||||
*/
|
||||
dev_warn(&intf->dev, "no suspend for driver %s?\n",
|
||||
driver->name);
|
||||
/* Later we will unbind the driver and reprobe */
|
||||
intf->needs_binding = 1;
|
||||
dev_warn(&intf->dev, "no %s for driver %s?\n",
|
||||
"suspend", driver->name);
|
||||
mark_quiesced(intf);
|
||||
}
|
||||
|
||||
@ -879,10 +976,12 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
|
||||
goto done;
|
||||
|
||||
/* Can't resume it if it doesn't have a driver. */
|
||||
if (intf->condition == USB_INTERFACE_UNBOUND) {
|
||||
status = -ENOTCONN;
|
||||
if (intf->condition == USB_INTERFACE_UNBOUND)
|
||||
goto done;
|
||||
|
||||
/* Don't resume if the interface is marked for rebinding */
|
||||
if (intf->needs_binding)
|
||||
goto done;
|
||||
}
|
||||
driver = to_usb_driver(intf->dev.driver);
|
||||
|
||||
if (reset_resume) {
|
||||
@ -892,7 +991,7 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
|
||||
dev_err(&intf->dev, "%s error %d\n",
|
||||
"reset_resume", status);
|
||||
} else {
|
||||
/* status = -EOPNOTSUPP; */
|
||||
intf->needs_binding = 1;
|
||||
dev_warn(&intf->dev, "no %s for driver %s?\n",
|
||||
"reset_resume", driver->name);
|
||||
}
|
||||
@ -903,7 +1002,7 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
|
||||
dev_err(&intf->dev, "%s error %d\n",
|
||||
"resume", status);
|
||||
} else {
|
||||
/* status = -EOPNOTSUPP; */
|
||||
intf->needs_binding = 1;
|
||||
dev_warn(&intf->dev, "no %s for driver %s?\n",
|
||||
"resume", driver->name);
|
||||
}
|
||||
@ -911,11 +1010,10 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
|
||||
|
||||
done:
|
||||
dev_vdbg(&intf->dev, "%s: status %d\n", __func__, status);
|
||||
if (status == 0)
|
||||
if (status == 0 && intf->condition == USB_INTERFACE_BOUND)
|
||||
mark_active(intf);
|
||||
|
||||
/* FIXME: Unbind the driver and reprobe if the resume failed
|
||||
* (not possible if auto_pm is set) */
|
||||
/* Later we will unbind the driver and/or reprobe, if necessary */
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -1173,11 +1271,8 @@ static int usb_resume_both(struct usb_device *udev)
|
||||
* then we're stuck. */
|
||||
status = usb_resume_device(udev);
|
||||
}
|
||||
} else {
|
||||
|
||||
/* Needed for reset-resume */
|
||||
} else if (udev->reset_resume)
|
||||
status = usb_resume_device(udev);
|
||||
}
|
||||
|
||||
if (status == 0 && udev->actconfig) {
|
||||
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
|
||||
@ -1474,6 +1569,7 @@ int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg)
|
||||
{
|
||||
int status;
|
||||
|
||||
do_unbind_rebind(udev, DO_UNBIND);
|
||||
usb_pm_lock(udev);
|
||||
udev->auto_pm = 0;
|
||||
status = usb_suspend_both(udev, msg);
|
||||
@ -1501,6 +1597,7 @@ int usb_external_resume_device(struct usb_device *udev)
|
||||
status = usb_resume_both(udev);
|
||||
udev->last_busy = jiffies;
|
||||
usb_pm_unlock(udev);
|
||||
do_unbind_rebind(udev, DO_REBIND);
|
||||
|
||||
/* Now that the device is awake, we can start trying to autosuspend
|
||||
* it again. */
|
||||
@ -1542,14 +1639,11 @@ static int usb_resume(struct device *dev)
|
||||
udev = to_usb_device(dev);
|
||||
|
||||
/* If udev->skip_sys_resume is set then udev was already suspended
|
||||
* when the system suspend started, so we don't want to resume
|
||||
* udev during this system wakeup. However a reset-resume counts
|
||||
* as a wakeup event, so allow a reset-resume to occur if remote
|
||||
* wakeup is enabled. */
|
||||
if (udev->skip_sys_resume) {
|
||||
if (!(udev->reset_resume && udev->do_remote_wakeup))
|
||||
return -EHOSTUNREACH;
|
||||
}
|
||||
* when the system sleep started, so we don't want to resume it
|
||||
* during this system wakeup.
|
||||
*/
|
||||
if (udev->skip_sys_resume)
|
||||
return 0;
|
||||
return usb_external_resume_device(udev);
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ int usb_create_ep_files(struct device *parent,
|
||||
retval = endpoint_get_minor(ep_dev);
|
||||
if (retval) {
|
||||
dev_err(parent, "can not allocate minor number for %s\n",
|
||||
ep_dev->dev.bus_id);
|
||||
dev_name(&ep_dev->dev));
|
||||
goto error_register;
|
||||
}
|
||||
|
||||
@ -307,7 +307,7 @@ int usb_create_ep_files(struct device *parent,
|
||||
ep_dev->dev.class = ep_class->class;
|
||||
ep_dev->dev.parent = parent;
|
||||
ep_dev->dev.release = ep_device_release;
|
||||
snprintf(ep_dev->dev.bus_id, BUS_ID_SIZE, "usbdev%d.%d_ep%02x",
|
||||
dev_set_name(&ep_dev->dev, "usbdev%d.%d_ep%02x",
|
||||
udev->bus->busnum, udev->devnum,
|
||||
endpoint->desc.bEndpointAddress);
|
||||
|
||||
|
@ -150,7 +150,7 @@ int usb_register_dev(struct usb_interface *intf,
|
||||
int retval = -EINVAL;
|
||||
int minor_base = class_driver->minor_base;
|
||||
int minor = 0;
|
||||
char name[BUS_ID_SIZE];
|
||||
char name[20];
|
||||
char *temp;
|
||||
|
||||
#ifdef CONFIG_USB_DYNAMIC_MINORS
|
||||
@ -190,9 +190,9 @@ int usb_register_dev(struct usb_interface *intf,
|
||||
intf->minor = minor;
|
||||
|
||||
/* create a usb class device for this usb interface */
|
||||
snprintf(name, BUS_ID_SIZE, class_driver->name, minor - minor_base);
|
||||
snprintf(name, sizeof(name), class_driver->name, minor - minor_base);
|
||||
temp = strrchr(name, '/');
|
||||
if (temp && (temp[1] != 0x00))
|
||||
if (temp && (temp[1] != '\0'))
|
||||
++temp;
|
||||
else
|
||||
temp = name;
|
||||
@ -227,7 +227,7 @@ void usb_deregister_dev(struct usb_interface *intf,
|
||||
struct usb_class_driver *class_driver)
|
||||
{
|
||||
int minor_base = class_driver->minor_base;
|
||||
char name[BUS_ID_SIZE];
|
||||
char name[20];
|
||||
|
||||
#ifdef CONFIG_USB_DYNAMIC_MINORS
|
||||
minor_base = 0;
|
||||
@ -242,7 +242,7 @@ void usb_deregister_dev(struct usb_interface *intf,
|
||||
usb_minors[intf->minor] = NULL;
|
||||
up_write(&minor_rwsem);
|
||||
|
||||
snprintf(name, BUS_ID_SIZE, class_driver->name, intf->minor - minor_base);
|
||||
snprintf(name, sizeof(name), class_driver->name, intf->minor - minor_base);
|
||||
device_destroy(usb_class->class, MKDEV(USB_MAJOR, intf->minor));
|
||||
intf->usb_dev = NULL;
|
||||
intf->minor = -1;
|
||||
|
@ -900,14 +900,14 @@ static int register_root_hub(struct usb_hcd *hcd)
|
||||
if (retval != sizeof usb_dev->descriptor) {
|
||||
mutex_unlock(&usb_bus_list_lock);
|
||||
dev_dbg (parent_dev, "can't read %s device descriptor %d\n",
|
||||
usb_dev->dev.bus_id, retval);
|
||||
dev_name(&usb_dev->dev), retval);
|
||||
return (retval < 0) ? retval : -EMSGSIZE;
|
||||
}
|
||||
|
||||
retval = usb_new_device (usb_dev);
|
||||
if (retval) {
|
||||
dev_err (parent_dev, "can't register root hub for %s, %d\n",
|
||||
usb_dev->dev.bus_id, retval);
|
||||
dev_name(&usb_dev->dev), retval);
|
||||
}
|
||||
mutex_unlock(&usb_bus_list_lock);
|
||||
|
||||
@ -1764,7 +1764,7 @@ EXPORT_SYMBOL_GPL (usb_hc_died);
|
||||
* If memory is unavailable, returns NULL.
|
||||
*/
|
||||
struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
|
||||
struct device *dev, char *bus_name)
|
||||
struct device *dev, const char *bus_name)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#include <linux/rwsem.h>
|
||||
|
||||
#define MAX_TOPO_LEVEL 6
|
||||
|
||||
/* This file contains declarations of usbcore internals that are mostly
|
||||
* used or exposed by Host Controller Drivers.
|
||||
*/
|
||||
@ -235,7 +237,7 @@ extern void usb_hcd_disable_endpoint(struct usb_device *udev,
|
||||
extern int usb_hcd_get_frame_number(struct usb_device *udev);
|
||||
|
||||
extern struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,
|
||||
struct device *dev, char *bus_name);
|
||||
struct device *dev, const char *bus_name);
|
||||
extern struct usb_hcd *usb_get_hcd(struct usb_hcd *hcd);
|
||||
extern void usb_put_hcd(struct usb_hcd *hcd);
|
||||
extern int usb_add_hcd(struct usb_hcd *hcd,
|
||||
|
@ -72,7 +72,6 @@ struct usb_hub {
|
||||
|
||||
unsigned limited_power:1;
|
||||
unsigned quiescing:1;
|
||||
unsigned activating:1;
|
||||
unsigned disconnected:1;
|
||||
|
||||
unsigned has_indicators:1;
|
||||
@ -131,6 +130,12 @@ MODULE_PARM_DESC(use_both_schemes,
|
||||
DECLARE_RWSEM(ehci_cf_port_reset_rwsem);
|
||||
EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
|
||||
|
||||
#define HUB_DEBOUNCE_TIMEOUT 1500
|
||||
#define HUB_DEBOUNCE_STEP 25
|
||||
#define HUB_DEBOUNCE_STABLE 100
|
||||
|
||||
|
||||
static int usb_reset_and_verify_device(struct usb_device *udev);
|
||||
|
||||
static inline char *portspeed(int portstatus)
|
||||
{
|
||||
@ -535,37 +540,6 @@ static void hub_power_on(struct usb_hub *hub)
|
||||
msleep(max(pgood_delay, (unsigned) 100));
|
||||
}
|
||||
|
||||
static void hub_quiesce(struct usb_hub *hub)
|
||||
{
|
||||
/* (nonblocking) khubd and related activity won't re-trigger */
|
||||
hub->quiescing = 1;
|
||||
hub->activating = 0;
|
||||
|
||||
/* (blocking) stop khubd and related activity */
|
||||
usb_kill_urb(hub->urb);
|
||||
if (hub->has_indicators)
|
||||
cancel_delayed_work_sync(&hub->leds);
|
||||
if (hub->tt.hub)
|
||||
cancel_work_sync(&hub->tt.kevent);
|
||||
}
|
||||
|
||||
static void hub_activate(struct usb_hub *hub)
|
||||
{
|
||||
int status;
|
||||
|
||||
hub->quiescing = 0;
|
||||
hub->activating = 1;
|
||||
|
||||
status = usb_submit_urb(hub->urb, GFP_NOIO);
|
||||
if (status < 0)
|
||||
dev_err(hub->intfdev, "activate --> %d\n", status);
|
||||
if (hub->has_indicators && blinkenlights)
|
||||
schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
|
||||
|
||||
/* scan all ports ASAP */
|
||||
kick_khubd(hub);
|
||||
}
|
||||
|
||||
static int hub_hub_status(struct usb_hub *hub,
|
||||
u16 *status, u16 *change)
|
||||
{
|
||||
@ -624,136 +598,149 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
|
||||
kick_khubd(hub);
|
||||
}
|
||||
|
||||
/* caller has locked the hub device */
|
||||
static void hub_stop(struct usb_hub *hub)
|
||||
{
|
||||
struct usb_device *hdev = hub->hdev;
|
||||
int i;
|
||||
enum hub_activation_type {
|
||||
HUB_INIT, HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME
|
||||
};
|
||||
|
||||
/* Disconnect all the children */
|
||||
for (i = 0; i < hdev->maxchild; ++i) {
|
||||
if (hdev->children[i])
|
||||
usb_disconnect(&hdev->children[i]);
|
||||
}
|
||||
hub_quiesce(hub);
|
||||
}
|
||||
|
||||
#define HUB_RESET 1
|
||||
#define HUB_RESUME 2
|
||||
#define HUB_RESET_RESUME 3
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* Try to identify which devices need USB-PERSIST handling */
|
||||
static int persistent_device(struct usb_device *udev)
|
||||
{
|
||||
int i;
|
||||
int retval;
|
||||
struct usb_host_config *actconfig;
|
||||
|
||||
/* Explicitly not marked persistent? */
|
||||
if (!udev->persist_enabled)
|
||||
return 0;
|
||||
|
||||
/* No active config? */
|
||||
actconfig = udev->actconfig;
|
||||
if (!actconfig)
|
||||
return 0;
|
||||
|
||||
/* FIXME! We should check whether it's open here or not! */
|
||||
|
||||
/*
|
||||
* Check that all the interface drivers have a
|
||||
* 'reset_resume' entrypoint
|
||||
*/
|
||||
retval = 0;
|
||||
for (i = 0; i < actconfig->desc.bNumInterfaces; i++) {
|
||||
struct usb_interface *intf;
|
||||
struct usb_driver *driver;
|
||||
|
||||
intf = actconfig->interface[i];
|
||||
if (!intf->dev.driver)
|
||||
continue;
|
||||
driver = to_usb_driver(intf->dev.driver);
|
||||
if (!driver->reset_resume)
|
||||
return 0;
|
||||
/*
|
||||
* We have at least one driver, and that one
|
||||
* has a reset_resume method.
|
||||
*/
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void hub_restart(struct usb_hub *hub, int type)
|
||||
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
|
||||
{
|
||||
struct usb_device *hdev = hub->hdev;
|
||||
int port1;
|
||||
int status;
|
||||
bool need_debounce_delay = false;
|
||||
|
||||
/* Check each of the children to see if they require
|
||||
* USB-PERSIST handling or disconnection. Also check
|
||||
* each unoccupied port to make sure it is still disabled.
|
||||
/* After a resume, port power should still be on.
|
||||
* For any other type of activation, turn it on.
|
||||
*/
|
||||
if (type != HUB_RESUME)
|
||||
hub_power_on(hub);
|
||||
|
||||
/* Check each port and set hub->change_bits to let khubd know
|
||||
* which ports need attention.
|
||||
*/
|
||||
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
|
||||
struct usb_device *udev = hdev->children[port1-1];
|
||||
int status = 0;
|
||||
u16 portstatus, portchange;
|
||||
|
||||
if (!udev || udev->state == USB_STATE_NOTATTACHED) {
|
||||
if (type != HUB_RESET) {
|
||||
status = hub_port_status(hub, port1,
|
||||
&portstatus, &portchange);
|
||||
if (status == 0 && (portstatus &
|
||||
USB_PORT_STAT_ENABLE))
|
||||
clear_port_feature(hdev, port1,
|
||||
USB_PORT_FEAT_ENABLE);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Was the power session lost while we were suspended? */
|
||||
portstatus = portchange = 0;
|
||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
||||
if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
|
||||
dev_dbg(hub->intfdev,
|
||||
"port %d: status %04x change %04x\n",
|
||||
port1, portstatus, portchange);
|
||||
|
||||
/* If the device is gone, khubd will handle it later */
|
||||
if (status == 0 && !(portstatus & USB_PORT_STAT_CONNECTION))
|
||||
continue;
|
||||
|
||||
/* For "USB_PERSIST"-enabled children we must
|
||||
* mark the child device for reset-resume and
|
||||
* turn off the various status changes to prevent
|
||||
* khubd from disconnecting it later.
|
||||
/* After anything other than HUB_RESUME (i.e., initialization
|
||||
* or any sort of reset), every port should be disabled.
|
||||
* Unconnected ports should likewise be disabled (paranoia),
|
||||
* and so should ports for which we have no usb_device.
|
||||
*/
|
||||
if (status == 0 && !(portstatus & USB_PORT_STAT_ENABLE) &&
|
||||
persistent_device(udev)) {
|
||||
if (portchange & USB_PORT_STAT_C_ENABLE)
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_ENABLE);
|
||||
if (portchange & USB_PORT_STAT_C_CONNECTION)
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_CONNECTION);
|
||||
udev->reset_resume = 1;
|
||||
if ((portstatus & USB_PORT_STAT_ENABLE) && (
|
||||
type != HUB_RESUME ||
|
||||
!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
||||
!udev ||
|
||||
udev->state == USB_STATE_NOTATTACHED)) {
|
||||
clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
|
||||
portstatus &= ~USB_PORT_STAT_ENABLE;
|
||||
}
|
||||
|
||||
/* Otherwise for a reset_resume we must disconnect the child,
|
||||
* but as we may not lock the child device here
|
||||
* we have to do a "logical" disconnect.
|
||||
*/
|
||||
else if (type == HUB_RESET_RESUME)
|
||||
hub_port_logical_disconnect(hub, port1);
|
||||
/* Clear status-change flags; we'll debounce later */
|
||||
if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
||||
need_debounce_delay = true;
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_CONNECTION);
|
||||
}
|
||||
if (portchange & USB_PORT_STAT_C_ENABLE) {
|
||||
need_debounce_delay = true;
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_ENABLE);
|
||||
}
|
||||
|
||||
if (!udev || udev->state == USB_STATE_NOTATTACHED) {
|
||||
/* Tell khubd to disconnect the device or
|
||||
* check for a new connection
|
||||
*/
|
||||
if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
|
||||
set_bit(port1, hub->change_bits);
|
||||
|
||||
} else if (portstatus & USB_PORT_STAT_ENABLE) {
|
||||
/* The power session apparently survived the resume.
|
||||
* If there was an overcurrent or suspend change
|
||||
* (i.e., remote wakeup request), have khubd
|
||||
* take care of it.
|
||||
*/
|
||||
if (portchange)
|
||||
set_bit(port1, hub->change_bits);
|
||||
|
||||
} else if (udev->persist_enabled) {
|
||||
#ifdef CONFIG_PM
|
||||
udev->reset_resume = 1;
|
||||
#endif
|
||||
set_bit(port1, hub->change_bits);
|
||||
|
||||
} else {
|
||||
/* The power session is gone; tell khubd */
|
||||
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
|
||||
set_bit(port1, hub->change_bits);
|
||||
}
|
||||
}
|
||||
|
||||
hub_activate(hub);
|
||||
/* If no port-status-change flags were set, we don't need any
|
||||
* debouncing. If flags were set we can try to debounce the
|
||||
* ports all at once right now, instead of letting khubd do them
|
||||
* one at a time later on.
|
||||
*
|
||||
* If any port-status changes do occur during this delay, khubd
|
||||
* will see them later and handle them normally.
|
||||
*/
|
||||
if (need_debounce_delay)
|
||||
msleep(HUB_DEBOUNCE_STABLE);
|
||||
|
||||
hub->quiescing = 0;
|
||||
|
||||
status = usb_submit_urb(hub->urb, GFP_NOIO);
|
||||
if (status < 0)
|
||||
dev_err(hub->intfdev, "activate --> %d\n", status);
|
||||
if (hub->has_indicators && blinkenlights)
|
||||
schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
|
||||
|
||||
/* Scan all ports that need attention */
|
||||
kick_khubd(hub);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
enum hub_quiescing_type {
|
||||
HUB_DISCONNECT, HUB_PRE_RESET, HUB_SUSPEND
|
||||
};
|
||||
|
||||
static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
|
||||
{
|
||||
struct usb_device *hdev = hub->hdev;
|
||||
int i;
|
||||
|
||||
/* khubd and related activity won't re-trigger */
|
||||
hub->quiescing = 1;
|
||||
|
||||
if (type != HUB_SUSPEND) {
|
||||
/* Disconnect all the children */
|
||||
for (i = 0; i < hdev->maxchild; ++i) {
|
||||
if (hdev->children[i])
|
||||
usb_disconnect(&hdev->children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Stop khubd and related activity */
|
||||
usb_kill_urb(hub->urb);
|
||||
if (hub->has_indicators)
|
||||
cancel_delayed_work_sync(&hub->leds);
|
||||
if (hub->tt.hub)
|
||||
cancel_work_sync(&hub->tt.kevent);
|
||||
}
|
||||
|
||||
/* caller has locked the hub device */
|
||||
static int hub_pre_reset(struct usb_interface *intf)
|
||||
{
|
||||
struct usb_hub *hub = usb_get_intfdata(intf);
|
||||
|
||||
hub_stop(hub);
|
||||
hub_quiesce(hub, HUB_PRE_RESET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -762,8 +749,7 @@ static int hub_post_reset(struct usb_interface *intf)
|
||||
{
|
||||
struct usb_hub *hub = usb_get_intfdata(intf);
|
||||
|
||||
hub_power_on(hub);
|
||||
hub_activate(hub);
|
||||
hub_activate(hub, HUB_POST_RESET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1009,8 +995,7 @@ static int hub_configure(struct usb_hub *hub,
|
||||
if (hub->has_indicators && blinkenlights)
|
||||
hub->indicator [0] = INDICATOR_CYCLE;
|
||||
|
||||
hub_power_on(hub);
|
||||
hub_activate(hub);
|
||||
hub_activate(hub, HUB_INIT);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
@ -1042,7 +1027,7 @@ static void hub_disconnect(struct usb_interface *intf)
|
||||
|
||||
/* Disconnect all children and quiesce the hub */
|
||||
hub->error = 0;
|
||||
hub_stop(hub);
|
||||
hub_quiesce(hub, HUB_DISCONNECT);
|
||||
|
||||
usb_set_intfdata (intf, NULL);
|
||||
|
||||
@ -1068,6 +1053,12 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
||||
desc = intf->cur_altsetting;
|
||||
hdev = interface_to_usbdev(intf);
|
||||
|
||||
if (hdev->level == MAX_TOPO_LEVEL) {
|
||||
dev_err(&intf->dev, "Unsupported bus topology: "
|
||||
"hub nested too deep\n");
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USB_OTG_BLACKLIST_HUB
|
||||
if (hdev->parent) {
|
||||
dev_warn(&intf->dev, "ignoring external hub\n");
|
||||
@ -1814,6 +1805,51 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
#define MASK_BITS (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION | \
|
||||
USB_PORT_STAT_SUSPEND)
|
||||
#define WANT_BITS (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION)
|
||||
|
||||
/* Determine whether the device on a port is ready for a normal resume,
|
||||
* is ready for a reset-resume, or should be disconnected.
|
||||
*/
|
||||
static int check_port_resume_type(struct usb_device *udev,
|
||||
struct usb_hub *hub, int port1,
|
||||
int status, unsigned portchange, unsigned portstatus)
|
||||
{
|
||||
/* Is the device still present? */
|
||||
if (status || (portstatus & MASK_BITS) != WANT_BITS) {
|
||||
if (status >= 0)
|
||||
status = -ENODEV;
|
||||
}
|
||||
|
||||
/* Can't do a normal resume if the port isn't enabled,
|
||||
* so try a reset-resume instead.
|
||||
*/
|
||||
else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume) {
|
||||
if (udev->persist_enabled)
|
||||
udev->reset_resume = 1;
|
||||
else
|
||||
status = -ENODEV;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
dev_dbg(hub->intfdev,
|
||||
"port %d status %04x.%04x after resume, %d\n",
|
||||
port1, portchange, portstatus, status);
|
||||
} else if (udev->reset_resume) {
|
||||
|
||||
/* Late port handoff can set status-change bits */
|
||||
if (portchange & USB_PORT_STAT_C_CONNECTION)
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_CONNECTION);
|
||||
if (portchange & USB_PORT_STAT_C_ENABLE)
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_ENABLE);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
|
||||
/*
|
||||
@ -1943,7 +1979,8 @@ static int finish_port_resume(struct usb_device *udev)
|
||||
* resumed.
|
||||
*/
|
||||
if (udev->reset_resume)
|
||||
status = usb_reset_device(udev);
|
||||
retry_reset_resume:
|
||||
status = usb_reset_and_verify_device(udev);
|
||||
|
||||
/* 10.5.4.5 says be sure devices in the tree are still there.
|
||||
* For now let's assume the device didn't go crazy on resume,
|
||||
@ -1954,6 +1991,13 @@ static int finish_port_resume(struct usb_device *udev)
|
||||
status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
|
||||
if (status >= 0)
|
||||
status = (status > 0 ? 0 : -ENODEV);
|
||||
|
||||
/* If a normal resume failed, try doing a reset-resume */
|
||||
if (status && !udev->reset_resume && udev->persist_enabled) {
|
||||
dev_dbg(&udev->dev, "retry with reset-resume\n");
|
||||
udev->reset_resume = 1;
|
||||
goto retry_reset_resume;
|
||||
}
|
||||
}
|
||||
|
||||
if (status) {
|
||||
@ -2002,7 +2046,7 @@ static int finish_port_resume(struct usb_device *udev)
|
||||
* to it will be lost. Using the USB_PERSIST facility, the device can be
|
||||
* made to appear as if it had not disconnected.
|
||||
*
|
||||
* This facility can be dangerous. Although usb_reset_device() makes
|
||||
* This facility can be dangerous. Although usb_reset_and_verify_device() makes
|
||||
* every effort to insure that the same device is present after the
|
||||
* reset as before, it cannot provide a 100% guarantee. Furthermore it's
|
||||
* quite possible for a device to remain unaltered but its media to be
|
||||
@ -2018,7 +2062,6 @@ int usb_port_resume(struct usb_device *udev)
|
||||
int port1 = udev->portnum;
|
||||
int status;
|
||||
u16 portchange, portstatus;
|
||||
unsigned mask_flags, want_flags;
|
||||
|
||||
/* Skip the initial Clear-Suspend step for a remote wakeup */
|
||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
||||
@ -2047,35 +2090,23 @@ int usb_port_resume(struct usb_device *udev)
|
||||
*/
|
||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
||||
|
||||
SuspendCleared:
|
||||
if (udev->reset_resume)
|
||||
want_flags = USB_PORT_STAT_POWER
|
||||
| USB_PORT_STAT_CONNECTION;
|
||||
else
|
||||
want_flags = USB_PORT_STAT_POWER
|
||||
| USB_PORT_STAT_CONNECTION
|
||||
| USB_PORT_STAT_ENABLE;
|
||||
mask_flags = want_flags | USB_PORT_STAT_SUSPEND;
|
||||
/* TRSMRCY = 10 msec */
|
||||
msleep(10);
|
||||
}
|
||||
|
||||
if (status < 0 || (portstatus & mask_flags) != want_flags) {
|
||||
dev_dbg(hub->intfdev,
|
||||
"port %d status %04x.%04x after resume, %d\n",
|
||||
port1, portchange, portstatus, status);
|
||||
if (status >= 0)
|
||||
status = -ENODEV;
|
||||
} else {
|
||||
if (portchange & USB_PORT_STAT_C_SUSPEND)
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_SUSPEND);
|
||||
/* TRSMRCY = 10 msec */
|
||||
msleep(10);
|
||||
}
|
||||
SuspendCleared:
|
||||
if (status == 0) {
|
||||
if (portchange & USB_PORT_STAT_C_SUSPEND)
|
||||
clear_port_feature(hub->hdev, port1,
|
||||
USB_PORT_FEAT_C_SUSPEND);
|
||||
}
|
||||
|
||||
clear_bit(port1, hub->busy_bits);
|
||||
if (!hub->hdev->parent && !hub->busy_bits[0])
|
||||
usb_enable_root_hub_irq(hub->hdev->bus);
|
||||
|
||||
status = check_port_resume_type(udev,
|
||||
hub, port1, status, portchange, portstatus);
|
||||
if (status == 0)
|
||||
status = finish_port_resume(udev);
|
||||
if (status < 0) {
|
||||
@ -2085,17 +2116,16 @@ int usb_port_resume(struct usb_device *udev)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* caller has locked udev */
|
||||
static int remote_wakeup(struct usb_device *udev)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
usb_lock_device(udev);
|
||||
if (udev->state == USB_STATE_SUSPENDED) {
|
||||
dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-");
|
||||
usb_mark_last_busy(udev);
|
||||
status = usb_external_resume_device(udev);
|
||||
}
|
||||
usb_unlock_device(udev);
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -2108,14 +2138,25 @@ int usb_port_suspend(struct usb_device *udev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* However we may need to do a reset-resume */
|
||||
|
||||
int usb_port_resume(struct usb_device *udev)
|
||||
{
|
||||
int status = 0;
|
||||
struct usb_hub *hub = hdev_to_hub(udev->parent);
|
||||
int port1 = udev->portnum;
|
||||
int status;
|
||||
u16 portchange, portstatus;
|
||||
|
||||
/* However we may need to do a reset-resume */
|
||||
if (udev->reset_resume) {
|
||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
||||
status = check_port_resume_type(udev,
|
||||
hub, port1, status, portchange, portstatus);
|
||||
|
||||
if (status) {
|
||||
dev_dbg(&udev->dev, "can't resume, status %d\n", status);
|
||||
hub_port_logical_disconnect(hub, port1);
|
||||
} else if (udev->reset_resume) {
|
||||
dev_dbg(&udev->dev, "reset-resume\n");
|
||||
status = usb_reset_device(udev);
|
||||
status = usb_reset_and_verify_device(udev);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@ -2149,7 +2190,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
|
||||
dev_dbg(&intf->dev, "%s\n", __func__);
|
||||
|
||||
/* stop khubd and related activity */
|
||||
hub_quiesce(hub);
|
||||
hub_quiesce(hub, HUB_SUSPEND);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2158,7 +2199,7 @@ static int hub_resume(struct usb_interface *intf)
|
||||
struct usb_hub *hub = usb_get_intfdata(intf);
|
||||
|
||||
dev_dbg(&intf->dev, "%s\n", __func__);
|
||||
hub_restart(hub, HUB_RESUME);
|
||||
hub_activate(hub, HUB_RESUME);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2167,8 +2208,7 @@ static int hub_reset_resume(struct usb_interface *intf)
|
||||
struct usb_hub *hub = usb_get_intfdata(intf);
|
||||
|
||||
dev_dbg(&intf->dev, "%s\n", __func__);
|
||||
hub_power_on(hub);
|
||||
hub_restart(hub, HUB_RESET_RESUME);
|
||||
hub_activate(hub, HUB_RESET_RESUME);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2218,11 +2258,6 @@ static inline int remote_wakeup(struct usb_device *udev)
|
||||
* every 25ms for transient disconnects. When the port status has been
|
||||
* unchanged for 100ms it returns the port status.
|
||||
*/
|
||||
|
||||
#define HUB_DEBOUNCE_TIMEOUT 1500
|
||||
#define HUB_DEBOUNCE_STEP 25
|
||||
#define HUB_DEBOUNCE_STABLE 100
|
||||
|
||||
static int hub_port_debounce(struct usb_hub *hub, int port1)
|
||||
{
|
||||
int ret;
|
||||
@ -2302,7 +2337,7 @@ static int hub_set_address(struct usb_device *udev, int devnum)
|
||||
* Returns device in USB_STATE_ADDRESS, except on error.
|
||||
*
|
||||
* If this is called for an already-existing device (as part of
|
||||
* usb_reset_device), the caller must own the device lock. For a
|
||||
* usb_reset_and_verify_device), the caller must own the device lock. For a
|
||||
* newly detected device that is not accessible through any global
|
||||
* pointers, it's not necessary to lock the device.
|
||||
*/
|
||||
@ -2619,7 +2654,7 @@ hub_power_remaining (struct usb_hub *hub)
|
||||
* This routine is called when:
|
||||
* a port connection-change occurs;
|
||||
* a port enable-change occurs (often caused by EMI);
|
||||
* usb_reset_device() encounters changed descriptors (as from
|
||||
* usb_reset_and_verify_device() encounters changed descriptors (as from
|
||||
* a firmware download)
|
||||
* caller already locked the hub
|
||||
*/
|
||||
@ -2629,9 +2664,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
||||
struct usb_device *hdev = hub->hdev;
|
||||
struct device *hub_dev = hub->intfdev;
|
||||
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
|
||||
u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
|
||||
unsigned wHubCharacteristics =
|
||||
le16_to_cpu(hub->descriptor->wHubCharacteristics);
|
||||
struct usb_device *udev;
|
||||
int status, i;
|
||||
|
||||
|
||||
dev_dbg (hub_dev,
|
||||
"port %d, status %04x, change %04x, %s\n",
|
||||
port1, portstatus, portchange, portspeed (portstatus));
|
||||
@ -2640,30 +2677,73 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
||||
set_port_led(hub, port1, HUB_LED_AUTO);
|
||||
hub->indicator[port1-1] = INDICATOR_AUTO;
|
||||
}
|
||||
|
||||
/* Disconnect any existing devices under this port */
|
||||
if (hdev->children[port1-1])
|
||||
usb_disconnect(&hdev->children[port1-1]);
|
||||
clear_bit(port1, hub->change_bits);
|
||||
|
||||
#ifdef CONFIG_USB_OTG
|
||||
/* during HNP, don't repeat the debounce */
|
||||
if (hdev->bus->is_b_host)
|
||||
portchange &= ~USB_PORT_STAT_C_CONNECTION;
|
||||
portchange &= ~(USB_PORT_STAT_C_CONNECTION |
|
||||
USB_PORT_STAT_C_ENABLE);
|
||||
#endif
|
||||
|
||||
if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
||||
/* Try to use the debounce delay for protection against
|
||||
* port-enable changes caused, for example, by EMI.
|
||||
*/
|
||||
if (portchange & (USB_PORT_STAT_C_CONNECTION |
|
||||
USB_PORT_STAT_C_ENABLE)) {
|
||||
status = hub_port_debounce(hub, port1);
|
||||
if (status < 0) {
|
||||
if (printk_ratelimit())
|
||||
dev_err (hub_dev, "connect-debounce failed, "
|
||||
"port %d disabled\n", port1);
|
||||
goto done;
|
||||
portstatus &= ~USB_PORT_STAT_CONNECTION;
|
||||
} else {
|
||||
portstatus = status;
|
||||
}
|
||||
portstatus = status;
|
||||
}
|
||||
|
||||
/* Return now if nothing is connected */
|
||||
/* Try to resuscitate an existing device */
|
||||
udev = hdev->children[port1-1];
|
||||
if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
|
||||
udev->state != USB_STATE_NOTATTACHED) {
|
||||
|
||||
usb_lock_device(udev);
|
||||
if (portstatus & USB_PORT_STAT_ENABLE) {
|
||||
status = 0; /* Nothing to do */
|
||||
} else if (!udev->persist_enabled) {
|
||||
status = -ENODEV; /* Mustn't resuscitate */
|
||||
|
||||
#ifdef CONFIG_USB_SUSPEND
|
||||
} else if (udev->state == USB_STATE_SUSPENDED) {
|
||||
/* For a suspended device, treat this as a
|
||||
* remote wakeup event.
|
||||
*/
|
||||
if (udev->do_remote_wakeup)
|
||||
status = remote_wakeup(udev);
|
||||
|
||||
/* Otherwise leave it be; devices can't tell the
|
||||
* difference between suspended and disabled.
|
||||
*/
|
||||
else
|
||||
status = 0;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
status = usb_reset_device(udev);
|
||||
}
|
||||
usb_unlock_device(udev);
|
||||
|
||||
if (status == 0) {
|
||||
clear_bit(port1, hub->change_bits);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Disconnect any existing devices under this port */
|
||||
if (udev)
|
||||
usb_disconnect(&hdev->children[port1-1]);
|
||||
clear_bit(port1, hub->change_bits);
|
||||
|
||||
/* Return now if debouncing failed or nothing is connected */
|
||||
if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
|
||||
|
||||
/* maybe switch power back on (e.g. root hub was reset) */
|
||||
@ -2677,7 +2757,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
||||
}
|
||||
|
||||
for (i = 0; i < SET_CONFIG_TRIES; i++) {
|
||||
struct usb_device *udev;
|
||||
|
||||
/* reallocate for each attempt, since references
|
||||
* to the previous one can escape in various ways
|
||||
@ -2858,7 +2937,7 @@ static void hub_events(void)
|
||||
/* If the hub has died, clean up after it */
|
||||
if (hdev->state == USB_STATE_NOTATTACHED) {
|
||||
hub->error = -ENODEV;
|
||||
hub_stop(hub);
|
||||
hub_quiesce(hub, HUB_DISCONNECT);
|
||||
goto loop;
|
||||
}
|
||||
|
||||
@ -2877,7 +2956,7 @@ static void hub_events(void)
|
||||
dev_dbg (hub_dev, "resetting for error %d\n",
|
||||
hub->error);
|
||||
|
||||
ret = usb_reset_composite_device(hdev, intf);
|
||||
ret = usb_reset_device(hdev);
|
||||
if (ret) {
|
||||
dev_dbg (hub_dev,
|
||||
"error resetting hub: %d\n", ret);
|
||||
@ -2894,7 +2973,7 @@ static void hub_events(void)
|
||||
continue;
|
||||
connect_change = test_bit(i, hub->change_bits);
|
||||
if (!test_and_clear_bit(i, hub->event_bits) &&
|
||||
!connect_change && !hub->activating)
|
||||
!connect_change)
|
||||
continue;
|
||||
|
||||
ret = hub_port_status(hub, i,
|
||||
@ -2902,11 +2981,6 @@ static void hub_events(void)
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
if (hub->activating && !hdev->children[i-1] &&
|
||||
(portstatus &
|
||||
USB_PORT_STAT_CONNECTION))
|
||||
connect_change = 1;
|
||||
|
||||
if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
||||
clear_port_feature(hdev, i,
|
||||
USB_PORT_FEAT_C_CONNECTION);
|
||||
@ -2941,11 +3015,16 @@ static void hub_events(void)
|
||||
}
|
||||
|
||||
if (portchange & USB_PORT_STAT_C_SUSPEND) {
|
||||
struct usb_device *udev;
|
||||
|
||||
clear_port_feature(hdev, i,
|
||||
USB_PORT_FEAT_C_SUSPEND);
|
||||
if (hdev->children[i-1]) {
|
||||
udev = hdev->children[i-1];
|
||||
if (udev) {
|
||||
usb_lock_device(udev);
|
||||
ret = remote_wakeup(hdev->
|
||||
children[i-1]);
|
||||
usb_unlock_device(udev);
|
||||
if (ret < 0)
|
||||
connect_change = 1;
|
||||
} else {
|
||||
@ -3002,8 +3081,6 @@ static void hub_events(void)
|
||||
}
|
||||
}
|
||||
|
||||
hub->activating = 0;
|
||||
|
||||
/* If this is a root hub, tell the HCD it's okay to
|
||||
* re-enable port-change interrupts now. */
|
||||
if (!hdev->parent && !hub->busy_bits[0])
|
||||
@ -3172,12 +3249,12 @@ static int descriptors_changed(struct usb_device *udev,
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_reset_device - perform a USB port reset to reinitialize a device
|
||||
* usb_reset_and_verify_device - perform a USB port reset to reinitialize a device
|
||||
* @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
|
||||
*
|
||||
* WARNING - don't use this routine to reset a composite device
|
||||
* (one with multiple interfaces owned by separate drivers)!
|
||||
* Use usb_reset_composite_device() instead.
|
||||
* Use usb_reset_device() instead.
|
||||
*
|
||||
* Do a port reset, reassign the device's address, and establish its
|
||||
* former operating configuration. If the reset fails, or the device's
|
||||
@ -3201,7 +3278,7 @@ static int descriptors_changed(struct usb_device *udev,
|
||||
* holding the device lock because these tasks should always call
|
||||
* usb_autopm_resume_device(), thereby preventing any unwanted autoresume.
|
||||
*/
|
||||
int usb_reset_device(struct usb_device *udev)
|
||||
static int usb_reset_and_verify_device(struct usb_device *udev)
|
||||
{
|
||||
struct usb_device *parent_hdev = udev->parent;
|
||||
struct usb_hub *parent_hub;
|
||||
@ -3289,26 +3366,28 @@ int usb_reset_device(struct usb_device *udev)
|
||||
hub_port_logical_disconnect(parent_hub, port1);
|
||||
return -ENODEV;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_reset_device);
|
||||
|
||||
/**
|
||||
* usb_reset_composite_device - warn interface drivers and perform a USB port reset
|
||||
* usb_reset_device - warn interface drivers and perform a USB port reset
|
||||
* @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
|
||||
* @iface: interface bound to the driver making the request (optional)
|
||||
*
|
||||
* Warns all drivers bound to registered interfaces (using their pre_reset
|
||||
* method), performs the port reset, and then lets the drivers know that
|
||||
* the reset is over (using their post_reset method).
|
||||
*
|
||||
* Return value is the same as for usb_reset_device().
|
||||
* Return value is the same as for usb_reset_and_verify_device().
|
||||
*
|
||||
* The caller must own the device lock. For example, it's safe to use
|
||||
* this from a driver probe() routine after downloading new firmware.
|
||||
* For calls that might not occur during probe(), drivers should lock
|
||||
* the device using usb_lock_device_for_reset().
|
||||
*
|
||||
* If an interface is currently being probed or disconnected, we assume
|
||||
* its driver knows how to handle resets. For all other interfaces,
|
||||
* if the driver doesn't have pre_reset and post_reset methods then
|
||||
* we attempt to unbind it and rebind afterward.
|
||||
*/
|
||||
int usb_reset_composite_device(struct usb_device *udev,
|
||||
struct usb_interface *iface)
|
||||
int usb_reset_device(struct usb_device *udev)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
@ -3324,40 +3403,47 @@ int usb_reset_composite_device(struct usb_device *udev,
|
||||
/* Prevent autosuspend during the reset */
|
||||
usb_autoresume_device(udev);
|
||||
|
||||
if (iface && iface->condition != USB_INTERFACE_BINDING)
|
||||
iface = NULL;
|
||||
|
||||
if (config) {
|
||||
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
|
||||
struct usb_interface *cintf = config->interface[i];
|
||||
struct usb_driver *drv;
|
||||
int unbind = 0;
|
||||
|
||||
if (cintf->dev.driver) {
|
||||
drv = to_usb_driver(cintf->dev.driver);
|
||||
if (drv->pre_reset)
|
||||
(drv->pre_reset)(cintf);
|
||||
/* FIXME: Unbind if pre_reset returns an error or isn't defined */
|
||||
if (drv->pre_reset && drv->post_reset)
|
||||
unbind = (drv->pre_reset)(cintf);
|
||||
else if (cintf->condition ==
|
||||
USB_INTERFACE_BOUND)
|
||||
unbind = 1;
|
||||
if (unbind)
|
||||
usb_forced_unbind_intf(cintf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = usb_reset_device(udev);
|
||||
ret = usb_reset_and_verify_device(udev);
|
||||
|
||||
if (config) {
|
||||
for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
|
||||
struct usb_interface *cintf = config->interface[i];
|
||||
struct usb_driver *drv;
|
||||
int rebind = cintf->needs_binding;
|
||||
|
||||
if (cintf->dev.driver) {
|
||||
if (!rebind && cintf->dev.driver) {
|
||||
drv = to_usb_driver(cintf->dev.driver);
|
||||
if (drv->post_reset)
|
||||
(drv->post_reset)(cintf);
|
||||
/* FIXME: Unbind if post_reset returns an error or isn't defined */
|
||||
rebind = (drv->post_reset)(cintf);
|
||||
else if (cintf->condition ==
|
||||
USB_INTERFACE_BOUND)
|
||||
rebind = 1;
|
||||
}
|
||||
if (rebind)
|
||||
usb_rebind_intf(cintf);
|
||||
}
|
||||
}
|
||||
|
||||
usb_autosuspend_device(udev);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_reset_composite_device);
|
||||
EXPORT_SYMBOL_GPL(usb_reset_device);
|
||||
|
@ -712,25 +712,11 @@ static void usbfs_add_device(struct usb_device *dev)
|
||||
|
||||
static void usbfs_remove_device(struct usb_device *dev)
|
||||
{
|
||||
struct dev_state *ds;
|
||||
struct siginfo sinfo;
|
||||
|
||||
if (dev->usbfs_dentry) {
|
||||
fs_remove_file (dev->usbfs_dentry);
|
||||
dev->usbfs_dentry = NULL;
|
||||
}
|
||||
while (!list_empty(&dev->filelist)) {
|
||||
ds = list_entry(dev->filelist.next, struct dev_state, list);
|
||||
wake_up_all(&ds->wait);
|
||||
list_del_init(&ds->list);
|
||||
if (ds->discsignr) {
|
||||
sinfo.si_signo = ds->discsignr;
|
||||
sinfo.si_errno = EPIPE;
|
||||
sinfo.si_code = SI_ASYNCIO;
|
||||
sinfo.si_addr = ds->disccontext;
|
||||
kill_pid_info_as_uid(ds->discsignr, &sinfo, ds->disc_pid, ds->disc_uid, ds->disc_euid, ds->secid);
|
||||
}
|
||||
}
|
||||
usb_fs_classdev_common_remove(dev);
|
||||
}
|
||||
|
||||
static int usbfs_notify(struct notifier_block *self, unsigned long action, void *dev)
|
||||
|
@ -400,7 +400,7 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev,
|
||||
if (usb_pipein(pipe))
|
||||
urb_flags |= URB_SHORT_NOT_OK;
|
||||
|
||||
for (i = 0; i < io->entries; i++) {
|
||||
for_each_sg(sg, sg, io->entries, i) {
|
||||
unsigned len;
|
||||
|
||||
io->urbs[i] = usb_alloc_urb(0, mem_flags);
|
||||
@ -434,17 +434,17 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev,
|
||||
* to prevent stale pointers and to help spot bugs.
|
||||
*/
|
||||
if (dma) {
|
||||
io->urbs[i]->transfer_dma = sg_dma_address(sg + i);
|
||||
len = sg_dma_len(sg + i);
|
||||
io->urbs[i]->transfer_dma = sg_dma_address(sg);
|
||||
len = sg_dma_len(sg);
|
||||
#if defined(CONFIG_HIGHMEM) || defined(CONFIG_GART_IOMMU)
|
||||
io->urbs[i]->transfer_buffer = NULL;
|
||||
#else
|
||||
io->urbs[i]->transfer_buffer = sg_virt(&sg[i]);
|
||||
io->urbs[i]->transfer_buffer = sg_virt(sg);
|
||||
#endif
|
||||
} else {
|
||||
/* hc may use _only_ transfer_buffer */
|
||||
io->urbs[i]->transfer_buffer = sg_virt(&sg[i]);
|
||||
len = sg[i].length;
|
||||
io->urbs[i]->transfer_buffer = sg_virt(sg);
|
||||
len = sg->length;
|
||||
}
|
||||
|
||||
if (length) {
|
||||
@ -1090,7 +1090,7 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
|
||||
if (!device_is_registered(&interface->dev))
|
||||
continue;
|
||||
dev_dbg(&dev->dev, "unregistering interface %s\n",
|
||||
interface->dev.bus_id);
|
||||
dev_name(&interface->dev));
|
||||
device_del(&interface->dev);
|
||||
usb_remove_sysfs_intf_files(interface);
|
||||
}
|
||||
@ -1476,7 +1476,7 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev,
|
||||
*
|
||||
* This call is synchronous. The calling context must be able to sleep,
|
||||
* must own the device lock, and must not hold the driver model's USB
|
||||
* bus mutex; usb device driver probe() methods cannot use this routine.
|
||||
* bus mutex; usb interface driver probe() methods cannot use this routine.
|
||||
*
|
||||
* Returns zero on success, or else the status code returned by the
|
||||
* underlying call that failed. On successful completion, each interface
|
||||
@ -1611,7 +1611,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
|
||||
intf->dev.dma_mask = dev->dev.dma_mask;
|
||||
device_initialize(&intf->dev);
|
||||
mark_quiesced(intf);
|
||||
sprintf(&intf->dev.bus_id[0], "%d-%s:%d.%d",
|
||||
dev_set_name(&intf->dev, "%d-%s:%d.%d",
|
||||
dev->bus->busnum, dev->devpath,
|
||||
configuration, alt->desc.bInterfaceNumber);
|
||||
}
|
||||
@ -1631,12 +1631,12 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
|
||||
|
||||
dev_dbg(&dev->dev,
|
||||
"adding %s (config #%d, interface %d)\n",
|
||||
intf->dev.bus_id, configuration,
|
||||
dev_name(&intf->dev), configuration,
|
||||
intf->cur_altsetting->desc.bInterfaceNumber);
|
||||
ret = device_add(&intf->dev);
|
||||
if (ret != 0) {
|
||||
dev_err(&dev->dev, "device_add(%s) --> %d\n",
|
||||
intf->dev.bus_id, ret);
|
||||
dev_name(&intf->dev), ret);
|
||||
continue;
|
||||
}
|
||||
usb_create_sysfs_intf_files(intf);
|
||||
|
@ -308,7 +308,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
|
||||
* by location for diagnostics, tools, driver model, etc. The
|
||||
* string is a path along hub ports, from the root. Each device's
|
||||
* dev->devpath will be stable until USB is re-cabled, and hubs
|
||||
* are often labeled with these port numbers. The bus_id isn't
|
||||
* are often labeled with these port numbers. The name isn't
|
||||
* as stable: bus->busnum changes easily from modprobe order,
|
||||
* cardbus or pci hotplugging, and so on.
|
||||
*/
|
||||
@ -316,7 +316,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
|
||||
dev->devpath[0] = '0';
|
||||
|
||||
dev->dev.parent = bus->controller;
|
||||
sprintf(&dev->dev.bus_id[0], "usb%d", bus->busnum);
|
||||
dev_set_name(&dev->dev, "usb%d", bus->busnum);
|
||||
root_hub = 1;
|
||||
} else {
|
||||
/* match any labeling on the hubs; it's one-based */
|
||||
@ -328,8 +328,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
|
||||
"%s.%d", parent->devpath, port1);
|
||||
|
||||
dev->dev.parent = &parent->dev;
|
||||
sprintf(&dev->dev.bus_id[0], "%d-%s",
|
||||
bus->busnum, dev->devpath);
|
||||
dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath);
|
||||
|
||||
/* hub driver sets up TT records */
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ extern int usb_choose_configuration(struct usb_device *udev);
|
||||
extern void usb_kick_khubd(struct usb_device *dev);
|
||||
extern int usb_match_device(struct usb_device *dev,
|
||||
const struct usb_device_id *id);
|
||||
extern void usb_forced_unbind_intf(struct usb_interface *intf);
|
||||
extern void usb_rebind_intf(struct usb_interface *intf);
|
||||
|
||||
extern int usb_hub_init(void);
|
||||
extern void usb_hub_cleanup(void);
|
||||
@ -140,26 +142,11 @@ extern struct usb_driver usbfs_driver;
|
||||
extern const struct file_operations usbfs_devices_fops;
|
||||
extern const struct file_operations usbdev_file_operations;
|
||||
extern void usbfs_conn_disc_event(void);
|
||||
extern void usb_fs_classdev_common_remove(struct usb_device *udev);
|
||||
|
||||
extern int usb_devio_init(void);
|
||||
extern void usb_devio_cleanup(void);
|
||||
|
||||
struct dev_state {
|
||||
struct list_head list; /* state list */
|
||||
struct usb_device *dev;
|
||||
struct file *file;
|
||||
spinlock_t lock; /* protects the async urb lists */
|
||||
struct list_head async_pending;
|
||||
struct list_head async_completed;
|
||||
wait_queue_head_t wait; /* wake up if a request completed */
|
||||
unsigned int discsignr;
|
||||
struct pid *disc_pid;
|
||||
uid_t disc_uid, disc_euid;
|
||||
void __user *disccontext;
|
||||
unsigned long ifclaimed;
|
||||
u32 secid;
|
||||
};
|
||||
|
||||
/* internal notify stuff */
|
||||
extern void usb_notify_add_device(struct usb_device *udev);
|
||||
extern void usb_notify_remove_device(struct usb_device *udev);
|
||||
|
@ -586,6 +586,20 @@ config USB_G_PRINTER
|
||||
For more information, see Documentation/usb/gadget_printer.txt
|
||||
which includes sample code for accessing the device file.
|
||||
|
||||
config USB_CDC_COMPOSITE
|
||||
tristate "CDC Composite Device (Ethernet and ACM)"
|
||||
depends on NET
|
||||
help
|
||||
This driver provides two functions in one configuration:
|
||||
a CDC Ethernet (ECM) link, and a CDC ACM (serial port) link.
|
||||
|
||||
This driver requires four bulk and two interrupt endpoints,
|
||||
plus the ability to handle altsettings. Not all peripheral
|
||||
controllers are that capable.
|
||||
|
||||
Say "y" to link the driver statically, or "m" to build a
|
||||
dynamically linked module.
|
||||
|
||||
# put drivers that need isochronous transfer support (for audio
|
||||
# or video class gadget drivers), or specific hardware, here.
|
||||
|
||||
|
@ -22,18 +22,22 @@ obj-$(CONFIG_USB_M66592) += m66592-udc.o
|
||||
#
|
||||
# USB gadget drivers
|
||||
#
|
||||
g_zero-objs := zero.o usbstring.o config.o epautoconf.o
|
||||
g_ether-objs := ether.o usbstring.o config.o epautoconf.o
|
||||
g_serial-objs := serial.o usbstring.o config.o epautoconf.o
|
||||
C_UTILS = composite.o usbstring.o config.o epautoconf.o
|
||||
|
||||
g_zero-objs := zero.o f_sourcesink.o f_loopback.o $(C_UTILS)
|
||||
g_ether-objs := ether.o u_ether.o f_subset.o f_ecm.o $(C_UTILS)
|
||||
g_serial-objs := serial.o u_serial.o f_acm.o f_serial.o $(C_UTILS)
|
||||
g_midi-objs := gmidi.o usbstring.o config.o epautoconf.o
|
||||
gadgetfs-objs := inode.o
|
||||
g_file_storage-objs := file_storage.o usbstring.o config.o \
|
||||
epautoconf.o
|
||||
g_printer-objs := printer.o usbstring.o config.o \
|
||||
epautoconf.o
|
||||
g_cdc-objs := cdc2.o u_ether.o f_ecm.o \
|
||||
u_serial.o f_acm.o $(C_UTILS)
|
||||
|
||||
ifeq ($(CONFIG_USB_ETH_RNDIS),y)
|
||||
g_ether-objs += rndis.o
|
||||
g_ether-objs += f_rndis.o rndis.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_USB_ZERO) += g_zero.o
|
||||
@ -43,4 +47,5 @@ obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o
|
||||
obj-$(CONFIG_USB_G_SERIAL) += g_serial.o
|
||||
obj-$(CONFIG_USB_G_PRINTER) += g_printer.o
|
||||
obj-$(CONFIG_USB_MIDI_GADGET) += g_midi.o
|
||||
obj-$(CONFIG_USB_CDC_COMPOSITE) += g_cdc.o
|
||||
|
||||
|
@ -3342,7 +3342,7 @@ static int udc_probe(struct udc *dev)
|
||||
spin_lock_init(&dev->lock);
|
||||
dev->gadget.ops = &udc_ops;
|
||||
|
||||
strcpy(dev->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&dev->gadget.dev, "gadget");
|
||||
dev->gadget.dev.release = gadget_release;
|
||||
dev->gadget.name = name;
|
||||
dev->gadget.name = name;
|
||||
|
@ -1687,6 +1687,19 @@ static int __init at91udc_probe(struct platform_device *pdev)
|
||||
udc->board.pullup_active_low);
|
||||
}
|
||||
|
||||
/* newer chips have more FIFO memory than rm9200 */
|
||||
if (cpu_is_at91sam9260()) {
|
||||
udc->ep[0].maxpacket = 64;
|
||||
udc->ep[3].maxpacket = 64;
|
||||
udc->ep[4].maxpacket = 512;
|
||||
udc->ep[5].maxpacket = 512;
|
||||
} else if (cpu_is_at91sam9261()) {
|
||||
udc->ep[3].maxpacket = 64;
|
||||
} else if (cpu_is_at91sam9263()) {
|
||||
udc->ep[0].maxpacket = 64;
|
||||
udc->ep[3].maxpacket = 64;
|
||||
}
|
||||
|
||||
udc->udp_baseaddr = ioremap(res->start, res->end - res->start + 1);
|
||||
if (!udc->udp_baseaddr) {
|
||||
retval = -ENOMEM;
|
||||
|
246
drivers/usb/gadget/cdc2.c
Normal file
246
drivers/usb/gadget/cdc2.c
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* cdc2.c -- CDC Composite driver, with ECM and ACM support
|
||||
*
|
||||
* Copyright (C) 2008 David Brownell
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/utsname.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
#include "u_serial.h"
|
||||
|
||||
|
||||
#define DRIVER_DESC "CDC Composite Gadget"
|
||||
#define DRIVER_VERSION "King Kamehameha Day 2008"
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!!
|
||||
* Instead: allocate your own, using normal USB-IF procedures.
|
||||
*/
|
||||
|
||||
/* Thanks to NetChip Technologies for donating this product ID.
|
||||
* It's for devices with only this composite CDC configuration.
|
||||
*/
|
||||
#define CDC_VENDOR_NUM 0x0525 /* NetChip */
|
||||
#define CDC_PRODUCT_NUM 0xa4aa /* CDC Composite: ECM + ACM */
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct usb_device_descriptor device_desc = {
|
||||
.bLength = sizeof device_desc,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
|
||||
.bcdUSB = __constant_cpu_to_le16(0x0200),
|
||||
|
||||
.bDeviceClass = USB_CLASS_COMM,
|
||||
.bDeviceSubClass = 0,
|
||||
.bDeviceProtocol = 0,
|
||||
/* .bMaxPacketSize0 = f(hardware) */
|
||||
|
||||
/* Vendor and product id can be overridden by module parameters. */
|
||||
.idVendor = __constant_cpu_to_le16(CDC_VENDOR_NUM),
|
||||
.idProduct = __constant_cpu_to_le16(CDC_PRODUCT_NUM),
|
||||
/* .bcdDevice = f(hardware) */
|
||||
/* .iManufacturer = DYNAMIC */
|
||||
/* .iProduct = DYNAMIC */
|
||||
/* NO SERIAL NUMBER */
|
||||
.bNumConfigurations = 1,
|
||||
};
|
||||
|
||||
static struct usb_otg_descriptor otg_descriptor = {
|
||||
.bLength = sizeof otg_descriptor,
|
||||
.bDescriptorType = USB_DT_OTG,
|
||||
|
||||
/* REVISIT SRP-only hardware is possible, although
|
||||
* it would not be called "OTG" ...
|
||||
*/
|
||||
.bmAttributes = USB_OTG_SRP | USB_OTG_HNP,
|
||||
};
|
||||
|
||||
static const struct usb_descriptor_header *otg_desc[] = {
|
||||
(struct usb_descriptor_header *) &otg_descriptor,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
||||
/* string IDs are assigned dynamically */
|
||||
|
||||
#define STRING_MANUFACTURER_IDX 0
|
||||
#define STRING_PRODUCT_IDX 1
|
||||
|
||||
static char manufacturer[50];
|
||||
|
||||
static struct usb_string strings_dev[] = {
|
||||
[STRING_MANUFACTURER_IDX].s = manufacturer,
|
||||
[STRING_PRODUCT_IDX].s = DRIVER_DESC,
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings stringtab_dev = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = strings_dev,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *dev_strings[] = {
|
||||
&stringtab_dev,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static u8 hostaddr[ETH_ALEN];
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* We _always_ have both CDC ECM and CDC ACM functions.
|
||||
*/
|
||||
static int __init cdc_do_config(struct usb_configuration *c)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (gadget_is_otg(c->cdev->gadget)) {
|
||||
c->descriptors = otg_desc;
|
||||
c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
|
||||
}
|
||||
|
||||
status = ecm_bind_config(c, hostaddr);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
status = acm_bind_config(c, 0);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct usb_configuration cdc_config_driver = {
|
||||
.label = "CDC Composite (ECM + ACM)",
|
||||
.bind = cdc_do_config,
|
||||
.bConfigurationValue = 1,
|
||||
/* .iConfiguration = DYNAMIC */
|
||||
.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
|
||||
.bMaxPower = 1, /* 2 mA, minimal */
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __init cdc_bind(struct usb_composite_dev *cdev)
|
||||
{
|
||||
int gcnum;
|
||||
struct usb_gadget *gadget = cdev->gadget;
|
||||
int status;
|
||||
|
||||
if (!can_support_ecm(cdev->gadget)) {
|
||||
ERROR(cdev, "controller '%s' not usable\n", gadget->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set up network link layer */
|
||||
status = gether_setup(cdev->gadget, hostaddr);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
/* set up serial link layer */
|
||||
status = gserial_setup(cdev->gadget, 1);
|
||||
if (status < 0)
|
||||
goto fail0;
|
||||
|
||||
gcnum = usb_gadget_controller_number(gadget);
|
||||
if (gcnum >= 0)
|
||||
device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum);
|
||||
else {
|
||||
/* We assume that can_support_ecm() tells the truth;
|
||||
* but if the controller isn't recognized at all then
|
||||
* that assumption is a bit more likely to be wrong.
|
||||
*/
|
||||
WARN(cdev, "controller '%s' not recognized; trying %s\n",
|
||||
gadget->name,
|
||||
cdc_config_driver.label);
|
||||
device_desc.bcdDevice =
|
||||
__constant_cpu_to_le16(0x0300 | 0x0099);
|
||||
}
|
||||
|
||||
|
||||
/* Allocate string descriptor numbers ... note that string
|
||||
* contents can be overridden by the composite_dev glue.
|
||||
*/
|
||||
|
||||
/* device descriptor strings: manufacturer, product */
|
||||
snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
|
||||
init_utsname()->sysname, init_utsname()->release,
|
||||
gadget->name);
|
||||
status = usb_string_id(cdev);
|
||||
if (status < 0)
|
||||
goto fail1;
|
||||
strings_dev[STRING_MANUFACTURER_IDX].id = status;
|
||||
device_desc.iManufacturer = status;
|
||||
|
||||
status = usb_string_id(cdev);
|
||||
if (status < 0)
|
||||
goto fail1;
|
||||
strings_dev[STRING_PRODUCT_IDX].id = status;
|
||||
device_desc.iProduct = status;
|
||||
|
||||
/* register our configuration */
|
||||
status = usb_add_config(cdev, &cdc_config_driver);
|
||||
if (status < 0)
|
||||
goto fail1;
|
||||
|
||||
INFO(cdev, "%s, version: " DRIVER_VERSION "\n", DRIVER_DESC);
|
||||
|
||||
return 0;
|
||||
|
||||
fail1:
|
||||
gserial_cleanup();
|
||||
fail0:
|
||||
gether_cleanup();
|
||||
return status;
|
||||
}
|
||||
|
||||
static int __exit cdc_unbind(struct usb_composite_dev *cdev)
|
||||
{
|
||||
gserial_cleanup();
|
||||
gether_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct usb_composite_driver cdc_driver = {
|
||||
.name = "g_cdc",
|
||||
.dev = &device_desc,
|
||||
.strings = dev_strings,
|
||||
.bind = cdc_bind,
|
||||
.unbind = __exit_p(cdc_unbind),
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int __init init(void)
|
||||
{
|
||||
return usb_composite_register(&cdc_driver);
|
||||
}
|
||||
module_init(init);
|
||||
|
||||
static void __exit cleanup(void)
|
||||
{
|
||||
usb_composite_unregister(&cdc_driver);
|
||||
}
|
||||
module_exit(cleanup);
|
1041
drivers/usb/gadget/composite.c
Normal file
1041
drivers/usb/gadget/composite.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -96,7 +96,7 @@ int usb_gadget_config_buf(
|
||||
/* config descriptor first */
|
||||
if (length < USB_DT_CONFIG_SIZE || !desc)
|
||||
return -EINVAL;
|
||||
*cp = *config;
|
||||
*cp = *config;
|
||||
|
||||
/* then interface/endpoint/class/vendor/... */
|
||||
len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf,
|
||||
@ -115,3 +115,77 @@ int usb_gadget_config_buf(
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_copy_descriptors - copy a vector of USB descriptors
|
||||
* @src: null-terminated vector to copy
|
||||
* Context: initialization code, which may sleep
|
||||
*
|
||||
* This makes a copy of a vector of USB descriptors. Its primary use
|
||||
* is to support usb_function objects which can have multiple copies,
|
||||
* each needing different descriptors. Functions may have static
|
||||
* tables of descriptors, which are used as templates and customized
|
||||
* with identifiers (for interfaces, strings, endpoints, and more)
|
||||
* as needed by a given function instance.
|
||||
*/
|
||||
struct usb_descriptor_header **__init
|
||||
usb_copy_descriptors(struct usb_descriptor_header **src)
|
||||
{
|
||||
struct usb_descriptor_header **tmp;
|
||||
unsigned bytes;
|
||||
unsigned n_desc;
|
||||
void *mem;
|
||||
struct usb_descriptor_header **ret;
|
||||
|
||||
/* count descriptors and their sizes; then add vector size */
|
||||
for (bytes = 0, n_desc = 0, tmp = src; *tmp; tmp++, n_desc++)
|
||||
bytes += (*tmp)->bLength;
|
||||
bytes += (n_desc + 1) * sizeof(*tmp);
|
||||
|
||||
mem = kmalloc(bytes, GFP_KERNEL);
|
||||
if (!mem)
|
||||
return NULL;
|
||||
|
||||
/* fill in pointers starting at "tmp",
|
||||
* to descriptors copied starting at "mem";
|
||||
* and return "ret"
|
||||
*/
|
||||
tmp = mem;
|
||||
ret = mem;
|
||||
mem += (n_desc + 1) * sizeof(*tmp);
|
||||
while (*src) {
|
||||
memcpy(mem, *src, (*src)->bLength);
|
||||
*tmp = mem;
|
||||
tmp++;
|
||||
mem += (*src)->bLength;
|
||||
src++;
|
||||
}
|
||||
*tmp = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_find_endpoint - find a copy of an endpoint descriptor
|
||||
* @src: original vector of descriptors
|
||||
* @copy: copy of @src
|
||||
* @ep: endpoint descriptor found in @src
|
||||
*
|
||||
* This returns the copy of the @match descriptor made for @copy. Its
|
||||
* intended use is to help remembering the endpoint descriptor to use
|
||||
* when enabling a given endpoint.
|
||||
*/
|
||||
struct usb_endpoint_descriptor *__init
|
||||
usb_find_endpoint(
|
||||
struct usb_descriptor_header **src,
|
||||
struct usb_descriptor_header **copy,
|
||||
struct usb_endpoint_descriptor *match
|
||||
)
|
||||
{
|
||||
while (*src) {
|
||||
if (*src == (void *) match)
|
||||
return (void *)*copy;
|
||||
src++;
|
||||
copy++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -862,7 +862,7 @@ static int dummy_udc_probe (struct platform_device *pdev)
|
||||
/* maybe claim OTG support, though we won't complete HNP */
|
||||
dum->gadget.is_otg = (dummy_to_hcd(dum)->self.otg_port != 0);
|
||||
|
||||
strcpy (dum->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&dum->gadget.dev, "gadget");
|
||||
dum->gadget.dev.parent = &pdev->dev;
|
||||
dum->gadget.dev.release = dummy_gadget_release;
|
||||
rc = device_register (&dum->gadget.dev);
|
||||
@ -1865,7 +1865,7 @@ static int dummy_hcd_probe(struct platform_device *pdev)
|
||||
|
||||
dev_info(&pdev->dev, "%s, driver " DRIVER_VERSION "\n", driver_desc);
|
||||
|
||||
hcd = usb_create_hcd(&dummy_hcd, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd(&dummy_hcd, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
the_controller = hcd_to_dummy (hcd);
|
||||
|
@ -159,6 +159,7 @@ ep_matches (
|
||||
/* MATCH!! */
|
||||
|
||||
/* report address */
|
||||
desc->bEndpointAddress &= USB_DIR_IN;
|
||||
if (isdigit (ep->name [2])) {
|
||||
u8 num = simple_strtol (&ep->name [2], NULL, 10);
|
||||
desc->bEndpointAddress |= num;
|
||||
|
File diff suppressed because it is too large
Load Diff
589
drivers/usb/gadget/f_acm.c
Normal file
589
drivers/usb/gadget/f_acm.c
Normal file
@ -0,0 +1,589 @@
|
||||
/*
|
||||
* f_acm.c -- USB CDC serial (ACM) function driver
|
||||
*
|
||||
* Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
|
||||
* Copyright (C) 2008 by David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
/* #define VERBOSE_DEBUG */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "u_serial.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This CDC ACM function support just wraps control functions and
|
||||
* notifications around the generic serial-over-usb code.
|
||||
*
|
||||
* Because CDC ACM is standardized by the USB-IF, many host operating
|
||||
* systems have drivers for it. Accordingly, ACM is the preferred
|
||||
* interop solution for serial-port type connections. The control
|
||||
* models are often not necessary, and in any case don't do much in
|
||||
* this bare-bones implementation.
|
||||
*
|
||||
* Note that even MS-Windows has some support for ACM. However, that
|
||||
* support is somewhat broken because when you use ACM in a composite
|
||||
* device, having multiple interfaces confuses the poor OS. It doesn't
|
||||
* seem to understand CDC Union descriptors. The new "association"
|
||||
* 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;
|
||||
u8 port_num;
|
||||
|
||||
struct usb_descriptor_header **fs_function;
|
||||
struct acm_ep_descs fs;
|
||||
struct usb_descriptor_header **hs_function;
|
||||
struct acm_ep_descs hs;
|
||||
|
||||
struct usb_ep *notify;
|
||||
struct usb_endpoint_descriptor *notify_desc;
|
||||
|
||||
struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */
|
||||
u16 port_handshake_bits;
|
||||
#define RS232_RTS (1 << 1) /* unused with full duplex */
|
||||
#define RS232_DTR (1 << 0) /* host is ready for data r/w */
|
||||
};
|
||||
|
||||
static inline struct f_acm *func_to_acm(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_acm, port.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* notification endpoint uses smallish and infrequent fixed-size messages */
|
||||
|
||||
#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */
|
||||
#define GS_NOTIFY_MAXPACKET 8
|
||||
|
||||
/* interface and class descriptors: */
|
||||
|
||||
static struct usb_interface_descriptor acm_control_interface_desc __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
|
||||
.bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor acm_data_interface_desc __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc acm_header_desc __initdata = {
|
||||
.bLength = sizeof(acm_header_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
.bcdCDC = __constant_cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_call_mgmt_descriptor
|
||||
acm_call_mgmt_descriptor __initdata = {
|
||||
.bLength = sizeof(acm_call_mgmt_descriptor),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
|
||||
.bmCapabilities = 0,
|
||||
/* .bDataInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_acm_descriptor acm_descriptor __initdata = {
|
||||
.bLength = sizeof(acm_descriptor),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ACM_TYPE,
|
||||
.bmCapabilities = (1 << 1),
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc acm_union_desc __initdata = {
|
||||
.bLength = sizeof(acm_union_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
/* .bMasterInterface0 = DYNAMIC */
|
||||
/* .bSlaveInterface0 = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor acm_fs_notify_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(GS_NOTIFY_MAXPACKET),
|
||||
.bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_fs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_fs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *acm_fs_function[] __initdata = {
|
||||
(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_fs_notify_desc,
|
||||
(struct usb_descriptor_header *) &acm_data_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_fs_in_desc,
|
||||
(struct usb_descriptor_header *) &acm_fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor acm_hs_notify_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(GS_NOTIFY_MAXPACKET),
|
||||
.bInterval = GS_LOG2_NOTIFY_INTERVAL+4,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_hs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor acm_hs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *acm_hs_function[] __initdata = {
|
||||
(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_data_interface_desc,
|
||||
(struct usb_descriptor_header *) &acm_hs_in_desc,
|
||||
(struct usb_descriptor_header *) &acm_hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
#define ACM_CTRL_IDX 0
|
||||
#define ACM_DATA_IDX 1
|
||||
|
||||
/* static strings, in UTF-8 */
|
||||
static struct usb_string acm_string_defs[] = {
|
||||
[ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)",
|
||||
[ACM_DATA_IDX].s = "CDC ACM Data",
|
||||
{ /* ZEROES END LIST */ },
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings acm_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = acm_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *acm_strings[] = {
|
||||
&acm_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ACM control ... data handling is delegated to tty library code.
|
||||
* The main task of this function is to activate and deactivate
|
||||
* that code based on device state; track parameters like line
|
||||
* speed, handshake state, and so on; and issue notifications.
|
||||
*/
|
||||
|
||||
static void acm_complete_set_line_coding(struct usb_ep *ep,
|
||||
struct usb_request *req)
|
||||
{
|
||||
struct f_acm *acm = ep->driver_data;
|
||||
struct usb_composite_dev *cdev = acm->port.func.config->cdev;
|
||||
|
||||
if (req->status != 0) {
|
||||
DBG(cdev, "acm ttyGS%d completion, err %d\n",
|
||||
acm->port_num, req->status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* normal completion */
|
||||
if (req->actual != sizeof(acm->port_line_coding)) {
|
||||
DBG(cdev, "acm ttyGS%d short resp, len %d\n",
|
||||
acm->port_num, req->actual);
|
||||
usb_ep_set_halt(ep);
|
||||
} else {
|
||||
struct usb_cdc_line_coding *value = req->buf;
|
||||
|
||||
/* REVISIT: we currently just remember this data.
|
||||
* If we change that, (a) validate it first, then
|
||||
* (b) update whatever hardware needs updating,
|
||||
* (c) worry about locking. This is information on
|
||||
* the order of 9600-8-N-1 ... most of which means
|
||||
* nothing unless we control a real RS232 line.
|
||||
*/
|
||||
acm->port_line_coding = *value;
|
||||
}
|
||||
}
|
||||
|
||||
static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything except
|
||||
* CDC class messages; interface activation uses set_alt().
|
||||
*/
|
||||
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||||
|
||||
/* SET_LINE_CODING ... just read and save what the host sends */
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_REQ_SET_LINE_CODING:
|
||||
if (w_length != sizeof(struct usb_cdc_line_coding)
|
||||
|| w_index != acm->ctrl_id)
|
||||
goto invalid;
|
||||
|
||||
value = w_length;
|
||||
cdev->gadget->ep0->driver_data = acm;
|
||||
req->complete = acm_complete_set_line_coding;
|
||||
break;
|
||||
|
||||
/* GET_LINE_CODING ... return what host sent, or initial value */
|
||||
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_REQ_GET_LINE_CODING:
|
||||
if (w_index != acm->ctrl_id)
|
||||
goto invalid;
|
||||
|
||||
value = min_t(unsigned, w_length,
|
||||
sizeof(struct usb_cdc_line_coding));
|
||||
memcpy(req->buf, &acm->port_line_coding, value);
|
||||
break;
|
||||
|
||||
/* SET_CONTROL_LINE_STATE ... save what the host sent */
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_REQ_SET_CONTROL_LINE_STATE:
|
||||
if (w_index != acm->ctrl_id)
|
||||
goto invalid;
|
||||
|
||||
value = 0;
|
||||
|
||||
/* FIXME we should not allow data to flow until the
|
||||
* host sets the RS232_DTR bit; and when it clears
|
||||
* that bit, we should return to that no-flow state.
|
||||
*/
|
||||
acm->port_handshake_bits = w_value;
|
||||
break;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n",
|
||||
acm->port_num, ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(cdev, "acm response on ttyGS%d, err %d\n",
|
||||
acm->port_num, value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
|
||||
if (intf == acm->ctrl_id) {
|
||||
/* REVISIT this may need more work when we start to
|
||||
* send notifications ...
|
||||
*/
|
||||
if (acm->notify->driver_data) {
|
||||
VDBG(cdev, "reset acm control interface %d\n", intf);
|
||||
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);
|
||||
}
|
||||
usb_ep_enable(acm->notify, acm->notify_desc);
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
gserial_connect(&acm->port, acm->port_num);
|
||||
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acm_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_acm *acm = func_to_acm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num);
|
||||
gserial_disconnect(&acm->port);
|
||||
usb_ep_disable(acm->notify);
|
||||
acm->notify->driver_data = NULL;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ACM function driver setup/binding */
|
||||
static int __init
|
||||
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);
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* allocate instance-specific interface IDs, and patch descriptors */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
acm->ctrl_id = status;
|
||||
|
||||
acm_control_interface_desc.bInterfaceNumber = status;
|
||||
acm_union_desc .bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
acm->data_id = status;
|
||||
|
||||
acm_data_interface_desc.bInterfaceNumber = status;
|
||||
acm_union_desc.bSlaveInterface0 = status;
|
||||
acm_call_mgmt_descriptor.bDataInterface = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
acm->port.in = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
acm->port.out = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
acm->notify = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->descriptors = usb_copy_descriptors(acm_fs_function);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* FIXME provide a callback for triggering notifications */
|
||||
|
||||
DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
acm->port_num,
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
acm->port.in->name, acm->port.out->name,
|
||||
acm->notify->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (acm->notify)
|
||||
acm->notify->driver_data = NULL;
|
||||
if (acm->port.out)
|
||||
acm->port.out->driver_data = NULL;
|
||||
if (acm->port.in)
|
||||
acm->port.in->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
acm_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
if (gadget_is_dualspeed(c->cdev->gadget))
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
usb_free_descriptors(f->descriptors);
|
||||
kfree(func_to_acm(f));
|
||||
}
|
||||
|
||||
/* Some controllers can't support CDC ACM ... */
|
||||
static inline bool can_support_cdc(struct usb_configuration *c)
|
||||
{
|
||||
/* SH3 doesn't support multiple interfaces */
|
||||
if (gadget_is_sh(c->cdev->gadget))
|
||||
return false;
|
||||
|
||||
/* sa1100 doesn't have a third interrupt endpoint */
|
||||
if (gadget_is_sa1100(c->cdev->gadget))
|
||||
return false;
|
||||
|
||||
/* everything else is *probably* fine ... */
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 __init acm_bind_config(struct usb_configuration *c, u8 port_num)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
acm = kzalloc(sizeof *acm, GFP_KERNEL);
|
||||
if (!acm)
|
||||
return -ENOMEM;
|
||||
|
||||
acm->port_num = port_num;
|
||||
|
||||
acm->port.func.name = "acm";
|
||||
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;
|
||||
}
|
833
drivers/usb/gadget/f_ecm.c
Normal file
833
drivers/usb/gadget/f_ecm.c
Normal file
@ -0,0 +1,833 @@
|
||||
/*
|
||||
* f_ecm.c -- USB CDC Ethernet (ECM) link function driver
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
|
||||
|
||||
/*
|
||||
* This function is a "CDC Ethernet Networking Control Model" (CDC ECM)
|
||||
* Ethernet link. The data transfer model is simple (packets sent and
|
||||
* received over bulk endpoints using normal short packet termination),
|
||||
* and the control model exposes various data and optional notifications.
|
||||
*
|
||||
* ECM is well standardized and (except for Microsoft) supported by most
|
||||
* operating systems with USB host support. It's the preferred interop
|
||||
* solution for Ethernet over USB, at least for firmware based solutions.
|
||||
* (Hardware solutions tend to be more minimalist.) A newer and simpler
|
||||
* "Ethernet Emulation Model" (CDC EEM) hasn't yet caught on.
|
||||
*
|
||||
* Note that ECM requires the use of "alternate settings" for its data
|
||||
* interface. This means that the set_alt() method has real work to do,
|
||||
* and also means that a get_alt() method is required.
|
||||
*/
|
||||
|
||||
struct ecm_ep_descs {
|
||||
struct usb_endpoint_descriptor *in;
|
||||
struct usb_endpoint_descriptor *out;
|
||||
struct usb_endpoint_descriptor *notify;
|
||||
};
|
||||
|
||||
enum ecm_notify_state {
|
||||
ECM_NOTIFY_NONE, /* don't notify */
|
||||
ECM_NOTIFY_CONNECT, /* issue CONNECT next */
|
||||
ECM_NOTIFY_SPEED, /* issue SPEED_CHANGE next */
|
||||
};
|
||||
|
||||
struct f_ecm {
|
||||
struct gether port;
|
||||
u8 ctrl_id, data_id;
|
||||
|
||||
char ethaddr[14];
|
||||
|
||||
struct usb_descriptor_header **fs_function;
|
||||
struct ecm_ep_descs fs;
|
||||
struct usb_descriptor_header **hs_function;
|
||||
struct ecm_ep_descs hs;
|
||||
|
||||
struct usb_ep *notify;
|
||||
struct usb_endpoint_descriptor *notify_desc;
|
||||
struct usb_request *notify_req;
|
||||
u8 notify_state;
|
||||
bool is_open;
|
||||
|
||||
/* FIXME is_open needs some irq-ish locking
|
||||
* ... possibly the same as port.ioport
|
||||
*/
|
||||
};
|
||||
|
||||
static inline struct f_ecm *func_to_ecm(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_ecm, port.func);
|
||||
}
|
||||
|
||||
/* peak (theoretical) bulk transfer rate in bits-per-second */
|
||||
static inline unsigned bitrate(struct usb_gadget *g)
|
||||
{
|
||||
if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
|
||||
return 13 * 512 * 8 * 1000 * 8;
|
||||
else
|
||||
return 19 * 64 * 1 * 1000 * 8;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Include the status endpoint if we can, even though it's optional.
|
||||
*
|
||||
* Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
|
||||
* packet, to simplify cancellation; and a big transfer interval, to
|
||||
* waste less bandwidth.
|
||||
*
|
||||
* Some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even
|
||||
* if they ignore the connect/disconnect notifications that real aether
|
||||
* can provide. More advanced cdc configurations might want to support
|
||||
* encapsulated commands (vendor-specific, using control-OUT).
|
||||
*/
|
||||
|
||||
#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */
|
||||
#define STATUS_BYTECOUNT 16 /* 8 byte header + data */
|
||||
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor ecm_control_intf __initdata = {
|
||||
.bLength = sizeof ecm_control_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
/* status endpoint is optional; this could be patched later */
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET,
|
||||
.bInterfaceProtocol = USB_CDC_PROTO_NONE,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc header_desc __initdata = {
|
||||
.bLength = sizeof header_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
|
||||
.bcdCDC = __constant_cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc ecm_union_desc __initdata = {
|
||||
.bLength = sizeof(ecm_union_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
/* .bMasterInterface0 = DYNAMIC */
|
||||
/* .bSlaveInterface0 = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_ether_desc ether_desc __initdata = {
|
||||
.bLength = sizeof ether_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ETHERNET_TYPE,
|
||||
|
||||
/* this descriptor actually adds value, surprise! */
|
||||
/* .iMACAddress = DYNAMIC */
|
||||
.bmEthernetStatistics = __constant_cpu_to_le32(0), /* no statistics */
|
||||
.wMaxSegmentSize = __constant_cpu_to_le16(ETH_FRAME_LEN),
|
||||
.wNumberMCFilters = __constant_cpu_to_le16(0),
|
||||
.bNumberPowerFilters = 0,
|
||||
};
|
||||
|
||||
/* the default data interface has no endpoints ... */
|
||||
|
||||
static struct usb_interface_descriptor ecm_data_nop_intf __initdata = {
|
||||
.bLength = sizeof ecm_data_nop_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 0,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* ... but the "real" data interface has two bulk endpoints */
|
||||
|
||||
static struct usb_interface_descriptor ecm_data_intf __initdata = {
|
||||
.bLength = sizeof ecm_data_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_notify_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT),
|
||||
.bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eth_fs_function[] __initdata = {
|
||||
/* CDC ECM control descriptors */
|
||||
(struct usb_descriptor_header *) &ecm_control_intf,
|
||||
(struct usb_descriptor_header *) &header_desc,
|
||||
(struct usb_descriptor_header *) &ecm_union_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
/* NOTE: status endpoint might need to be removed */
|
||||
(struct usb_descriptor_header *) &fs_notify_desc,
|
||||
/* data interface, altsettings 0 and 1 */
|
||||
(struct usb_descriptor_header *) &ecm_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &ecm_data_intf,
|
||||
(struct usb_descriptor_header *) &fs_in_desc,
|
||||
(struct usb_descriptor_header *) &fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_notify_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT),
|
||||
.bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
|
||||
};
|
||||
static struct usb_endpoint_descriptor hs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eth_hs_function[] __initdata = {
|
||||
/* CDC ECM control descriptors */
|
||||
(struct usb_descriptor_header *) &ecm_control_intf,
|
||||
(struct usb_descriptor_header *) &header_desc,
|
||||
(struct usb_descriptor_header *) &ecm_union_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
/* NOTE: status endpoint might need to be removed */
|
||||
(struct usb_descriptor_header *) &hs_notify_desc,
|
||||
/* data interface, altsettings 0 and 1 */
|
||||
(struct usb_descriptor_header *) &ecm_data_nop_intf,
|
||||
(struct usb_descriptor_header *) &ecm_data_intf,
|
||||
(struct usb_descriptor_header *) &hs_in_desc,
|
||||
(struct usb_descriptor_header *) &hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string ecm_string_defs[] = {
|
||||
[0].s = "CDC Ethernet Control Model (ECM)",
|
||||
[1].s = NULL /* DYNAMIC */,
|
||||
[2].s = "CDC Ethernet Data",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings ecm_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = ecm_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *ecm_strings[] = {
|
||||
&ecm_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void ecm_do_notify(struct f_ecm *ecm)
|
||||
{
|
||||
struct usb_request *req = ecm->notify_req;
|
||||
struct usb_cdc_notification *event;
|
||||
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
|
||||
__le32 *data;
|
||||
int status;
|
||||
|
||||
/* notification already in flight? */
|
||||
if (!req)
|
||||
return;
|
||||
|
||||
event = req->buf;
|
||||
switch (ecm->notify_state) {
|
||||
case ECM_NOTIFY_NONE:
|
||||
return;
|
||||
|
||||
case ECM_NOTIFY_CONNECT:
|
||||
event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
|
||||
if (ecm->is_open)
|
||||
event->wValue = cpu_to_le16(1);
|
||||
else
|
||||
event->wValue = cpu_to_le16(0);
|
||||
event->wLength = 0;
|
||||
req->length = sizeof *event;
|
||||
|
||||
DBG(cdev, "notify connect %s\n",
|
||||
ecm->is_open ? "true" : "false");
|
||||
ecm->notify_state = ECM_NOTIFY_SPEED;
|
||||
break;
|
||||
|
||||
case ECM_NOTIFY_SPEED:
|
||||
event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
|
||||
event->wValue = cpu_to_le16(0);
|
||||
event->wLength = cpu_to_le16(8);
|
||||
req->length = STATUS_BYTECOUNT;
|
||||
|
||||
/* SPEED_CHANGE data is up/down speeds in bits/sec */
|
||||
data = req->buf + sizeof *event;
|
||||
data[0] = cpu_to_le32(bitrate(cdev->gadget));
|
||||
data[1] = data[0];
|
||||
|
||||
DBG(cdev, "notify speed %d\n", bitrate(cdev->gadget));
|
||||
ecm->notify_state = ECM_NOTIFY_NONE;
|
||||
break;
|
||||
}
|
||||
event->bmRequestType = 0xA1;
|
||||
event->wIndex = cpu_to_le16(ecm->ctrl_id);
|
||||
|
||||
ecm->notify_req = NULL;
|
||||
status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC);
|
||||
if (status < 0) {
|
||||
ecm->notify_req = req;
|
||||
DBG(cdev, "notify --> %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
static void ecm_notify(struct f_ecm *ecm)
|
||||
{
|
||||
/* NOTE on most versions of Linux, host side cdc-ethernet
|
||||
* won't listen for notifications until its netdevice opens.
|
||||
* The first notification then sits in the FIFO for a long
|
||||
* time, and the second one is queued.
|
||||
*/
|
||||
ecm->notify_state = ECM_NOTIFY_CONNECT;
|
||||
ecm_do_notify(ecm);
|
||||
}
|
||||
|
||||
static void ecm_notify_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_ecm *ecm = req->context;
|
||||
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
|
||||
struct usb_cdc_notification *event = req->buf;
|
||||
|
||||
switch (req->status) {
|
||||
case 0:
|
||||
/* no fault */
|
||||
break;
|
||||
case -ECONNRESET:
|
||||
case -ESHUTDOWN:
|
||||
ecm->notify_state = ECM_NOTIFY_NONE;
|
||||
break;
|
||||
default:
|
||||
DBG(cdev, "event %02x --> %d\n",
|
||||
event->bNotificationType, req->status);
|
||||
break;
|
||||
}
|
||||
ecm->notify_req = req;
|
||||
ecm_do_notify(ecm);
|
||||
}
|
||||
|
||||
static int ecm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything except
|
||||
* CDC class messages; interface activation uses set_alt().
|
||||
*/
|
||||
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_SET_ETHERNET_PACKET_FILTER:
|
||||
/* see 6.2.30: no data, wIndex = interface,
|
||||
* wValue = packet filter bitmap
|
||||
*/
|
||||
if (w_length != 0 || w_index != ecm->ctrl_id)
|
||||
goto invalid;
|
||||
DBG(cdev, "packet filter %02x\n", w_value);
|
||||
/* REVISIT locking of cdc_filter. This assumes the UDC
|
||||
* driver won't have a concurrent packet TX irq running on
|
||||
* another CPU; or that if it does, this write is atomic...
|
||||
*/
|
||||
ecm->port.cdc_filter = w_value;
|
||||
value = 0;
|
||||
break;
|
||||
|
||||
/* and optionally:
|
||||
* case USB_CDC_SEND_ENCAPSULATED_COMMAND:
|
||||
* case USB_CDC_GET_ENCAPSULATED_RESPONSE:
|
||||
* case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS:
|
||||
* case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER:
|
||||
* case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER:
|
||||
* case USB_CDC_GET_ETHERNET_STATISTIC:
|
||||
*/
|
||||
|
||||
default:
|
||||
invalid:
|
||||
DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
DBG(cdev, "ecm req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(cdev, "ecm req %02x.%02x response err %d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* Control interface has only altsetting 0 */
|
||||
if (intf == ecm->ctrl_id) {
|
||||
if (alt != 0)
|
||||
goto fail;
|
||||
|
||||
if (ecm->notify->driver_data) {
|
||||
VDBG(cdev, "reset ecm control %d\n", intf);
|
||||
usb_ep_disable(ecm->notify);
|
||||
} else {
|
||||
VDBG(cdev, "init ecm ctrl %d\n", intf);
|
||||
ecm->notify_desc = ep_choose(cdev->gadget,
|
||||
ecm->hs.notify,
|
||||
ecm->fs.notify);
|
||||
}
|
||||
usb_ep_enable(ecm->notify, ecm->notify_desc);
|
||||
ecm->notify->driver_data = ecm;
|
||||
|
||||
/* Data interface has two altsettings, 0 and 1 */
|
||||
} else if (intf == ecm->data_id) {
|
||||
if (alt > 1)
|
||||
goto fail;
|
||||
|
||||
if (ecm->port.in_ep->driver_data) {
|
||||
DBG(cdev, "reset ecm\n");
|
||||
gether_disconnect(&ecm->port);
|
||||
}
|
||||
|
||||
if (!ecm->port.in) {
|
||||
DBG(cdev, "init ecm\n");
|
||||
ecm->port.in = ep_choose(cdev->gadget,
|
||||
ecm->hs.in, ecm->fs.in);
|
||||
ecm->port.out = ep_choose(cdev->gadget,
|
||||
ecm->hs.out, ecm->fs.out);
|
||||
}
|
||||
|
||||
/* CDC Ethernet only sends data in non-default altsettings.
|
||||
* Changing altsettings resets filters, statistics, etc.
|
||||
*/
|
||||
if (alt == 1) {
|
||||
struct net_device *net;
|
||||
|
||||
/* Enable zlps by default for ECM conformance;
|
||||
* override for musb_hdrc (avoids txdma ovhead)
|
||||
* and sa1100 (can't).
|
||||
*/
|
||||
ecm->port.is_zlp_ok = !(
|
||||
gadget_is_sa1100(cdev->gadget)
|
||||
|| gadget_is_musbhdrc(cdev->gadget)
|
||||
);
|
||||
ecm->port.cdc_filter = DEFAULT_FILTER;
|
||||
DBG(cdev, "activate ecm\n");
|
||||
net = gether_connect(&ecm->port);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
}
|
||||
|
||||
/* NOTE this can be a minor disagreement with the ECM spec,
|
||||
* which says speed notifications will "always" follow
|
||||
* connection notifications. But we allow one connect to
|
||||
* follow another (if the first is in flight), and instead
|
||||
* just guarantee that a speed notification is always sent.
|
||||
*/
|
||||
ecm_notify(ecm);
|
||||
} else
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Because the data interface supports multiple altsettings,
|
||||
* this ECM function *MUST* implement a get_alt() method.
|
||||
*/
|
||||
static int ecm_get_alt(struct usb_function *f, unsigned intf)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
|
||||
if (intf == ecm->ctrl_id)
|
||||
return 0;
|
||||
return ecm->port.in_ep->driver_data ? 1 : 0;
|
||||
}
|
||||
|
||||
static void ecm_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "ecm deactivated\n");
|
||||
|
||||
if (ecm->port.in_ep->driver_data)
|
||||
gether_disconnect(&ecm->port);
|
||||
|
||||
if (ecm->notify->driver_data) {
|
||||
usb_ep_disable(ecm->notify);
|
||||
ecm->notify->driver_data = NULL;
|
||||
ecm->notify_desc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Callbacks let us notify the host about connect/disconnect when the
|
||||
* net device is opened or closed.
|
||||
*
|
||||
* For testing, note that link states on this side include both opened
|
||||
* and closed variants of:
|
||||
*
|
||||
* - disconnected/unconfigured
|
||||
* - configured but inactive (data alt 0)
|
||||
* - configured and active (data alt 1)
|
||||
*
|
||||
* Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and
|
||||
* SET_INTERFACE (altsetting). Remember also that "configured" doesn't
|
||||
* imply the host is actually polling the notification endpoint, and
|
||||
* likewise that "active" doesn't imply it's actually using the data
|
||||
* endpoints for traffic.
|
||||
*/
|
||||
|
||||
static void ecm_open(struct gether *geth)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(&geth->func);
|
||||
|
||||
DBG(ecm->port.func.config->cdev, "%s\n", __func__);
|
||||
|
||||
ecm->is_open = true;
|
||||
ecm_notify(ecm);
|
||||
}
|
||||
|
||||
static void ecm_close(struct gether *geth)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(&geth->func);
|
||||
|
||||
DBG(ecm->port.func.config->cdev, "%s\n", __func__);
|
||||
|
||||
ecm->is_open = false;
|
||||
ecm_notify(ecm);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ethernet function driver setup/binding */
|
||||
|
||||
static int __init
|
||||
ecm_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ecm->ctrl_id = status;
|
||||
|
||||
ecm_control_intf.bInterfaceNumber = status;
|
||||
ecm_union_desc.bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
ecm->data_id = status;
|
||||
|
||||
ecm_data_nop_intf.bInterfaceNumber = status;
|
||||
ecm_data_intf.bInterfaceNumber = status;
|
||||
ecm_union_desc.bSlaveInterface0 = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ecm->port.in_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ecm->port.out_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* NOTE: a status/notification endpoint is *OPTIONAL* but we
|
||||
* don't treat it that way. It's simpler, and some newer CDC
|
||||
* profiles (wireless handsets) no longer treat it as optional.
|
||||
*/
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
ecm->notify = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
status = -ENOMEM;
|
||||
|
||||
/* allocate notification request and buffer */
|
||||
ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
|
||||
if (!ecm->notify_req)
|
||||
goto fail;
|
||||
ecm->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
|
||||
if (!ecm->notify_req->buf)
|
||||
goto fail;
|
||||
ecm->notify_req->context = ecm;
|
||||
ecm->notify_req->complete = ecm_notify_complete;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->descriptors = usb_copy_descriptors(eth_fs_function);
|
||||
if (!f->descriptors)
|
||||
goto fail;
|
||||
|
||||
ecm->fs.in = usb_find_endpoint(eth_fs_function,
|
||||
f->descriptors, &fs_in_desc);
|
||||
ecm->fs.out = usb_find_endpoint(eth_fs_function,
|
||||
f->descriptors, &fs_out_desc);
|
||||
ecm->fs.notify = usb_find_endpoint(eth_fs_function,
|
||||
f->descriptors, &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)) {
|
||||
hs_in_desc.bEndpointAddress =
|
||||
fs_in_desc.bEndpointAddress;
|
||||
hs_out_desc.bEndpointAddress =
|
||||
fs_out_desc.bEndpointAddress;
|
||||
hs_notify_desc.bEndpointAddress =
|
||||
fs_notify_desc.bEndpointAddress;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->hs_descriptors = usb_copy_descriptors(eth_hs_function);
|
||||
if (!f->hs_descriptors)
|
||||
goto fail;
|
||||
|
||||
ecm->hs.in = usb_find_endpoint(eth_hs_function,
|
||||
f->hs_descriptors, &hs_in_desc);
|
||||
ecm->hs.out = usb_find_endpoint(eth_hs_function,
|
||||
f->hs_descriptors, &hs_out_desc);
|
||||
ecm->hs.notify = usb_find_endpoint(eth_hs_function,
|
||||
f->hs_descriptors, &hs_notify_desc);
|
||||
}
|
||||
|
||||
/* NOTE: all that is done without knowing or caring about
|
||||
* the network link ... which is unavailable to this code
|
||||
* until we're activated via set_alt().
|
||||
*/
|
||||
|
||||
ecm->port.open = ecm_open;
|
||||
ecm->port.close = ecm_close;
|
||||
|
||||
DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
ecm->port.in_ep->name, ecm->port.out_ep->name,
|
||||
ecm->notify->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (f->descriptors)
|
||||
usb_free_descriptors(f->descriptors);
|
||||
|
||||
if (ecm->notify_req) {
|
||||
kfree(ecm->notify_req->buf);
|
||||
usb_ep_free_request(ecm->notify, ecm->notify_req);
|
||||
}
|
||||
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (ecm->notify)
|
||||
ecm->notify->driver_data = NULL;
|
||||
if (ecm->port.out)
|
||||
ecm->port.out_ep->driver_data = NULL;
|
||||
if (ecm->port.in)
|
||||
ecm->port.in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
ecm_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_ecm *ecm = func_to_ecm(f);
|
||||
|
||||
DBG(c->cdev, "ecm unbind\n");
|
||||
|
||||
if (gadget_is_dualspeed(c->cdev->gadget))
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
usb_free_descriptors(f->descriptors);
|
||||
|
||||
kfree(ecm->notify_req->buf);
|
||||
usb_ep_free_request(ecm->notify, ecm->notify_req);
|
||||
|
||||
ecm_string_defs[1].s = NULL;
|
||||
kfree(ecm);
|
||||
}
|
||||
|
||||
/**
|
||||
* ecm_bind_config - add CDC Ethernet network link to a configuration
|
||||
* @c: the configuration to support the network link
|
||||
* @ethaddr: a buffer in which the ethernet address of the host side
|
||||
* side of the link was recorded
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*
|
||||
* Caller must have called @gether_setup(). Caller is also responsible
|
||||
* for calling @gether_cleanup() before module unload.
|
||||
*/
|
||||
int __init ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
|
||||
{
|
||||
struct f_ecm *ecm;
|
||||
int status;
|
||||
|
||||
if (!can_support_ecm(c->cdev->gadget) || !ethaddr)
|
||||
return -EINVAL;
|
||||
|
||||
/* maybe allocate device-global string IDs */
|
||||
if (ecm_string_defs[0].id == 0) {
|
||||
|
||||
/* control interface label */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
ecm_string_defs[0].id = status;
|
||||
ecm_control_intf.iInterface = status;
|
||||
|
||||
/* data interface label */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
ecm_string_defs[2].id = status;
|
||||
ecm_data_intf.iInterface = status;
|
||||
|
||||
/* MAC address */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
ecm_string_defs[1].id = status;
|
||||
ether_desc.iMACAddress = status;
|
||||
}
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
ecm = kzalloc(sizeof *ecm, GFP_KERNEL);
|
||||
if (!ecm)
|
||||
return -ENOMEM;
|
||||
|
||||
/* export host's Ethernet address in CDC format */
|
||||
snprintf(ecm->ethaddr, sizeof ecm->ethaddr,
|
||||
"%02X%02X%02X%02X%02X%02X",
|
||||
ethaddr[0], ethaddr[1], ethaddr[2],
|
||||
ethaddr[3], ethaddr[4], ethaddr[5]);
|
||||
ecm_string_defs[1].s = ecm->ethaddr;
|
||||
|
||||
ecm->port.cdc_filter = DEFAULT_FILTER;
|
||||
|
||||
ecm->port.func.name = "cdc_ethernet";
|
||||
ecm->port.func.strings = ecm_strings;
|
||||
/* descriptors are per-instance copies */
|
||||
ecm->port.func.bind = ecm_bind;
|
||||
ecm->port.func.unbind = ecm_unbind;
|
||||
ecm->port.func.set_alt = ecm_set_alt;
|
||||
ecm->port.func.get_alt = ecm_get_alt;
|
||||
ecm->port.func.setup = ecm_setup;
|
||||
ecm->port.func.disable = ecm_disable;
|
||||
|
||||
status = usb_add_function(c, &ecm->port.func);
|
||||
if (status) {
|
||||
ecm_string_defs[1].s = NULL;
|
||||
kfree(ecm);
|
||||
}
|
||||
return status;
|
||||
}
|
381
drivers/usb/gadget/f_loopback.c
Normal file
381
drivers/usb/gadget/f_loopback.c
Normal file
@ -0,0 +1,381 @@
|
||||
/*
|
||||
* f_loopback.c - USB peripheral loopback configuration driver
|
||||
*
|
||||
* Copyright (C) 2003-2008 David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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/utsname.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "g_zero.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* LOOPBACK FUNCTION ... a testing vehicle for USB peripherals,
|
||||
*
|
||||
* This takes messages of various sizes written OUT to a device, and loops
|
||||
* them back so they can be read IN from it. It has been used by certain
|
||||
* test applications. It supports limited testing of data queueing logic.
|
||||
*
|
||||
*
|
||||
* This is currently packaged as a configuration driver, which can't be
|
||||
* combined with other functions to make composite devices. However, it
|
||||
* can be combined with other independent configurations.
|
||||
*/
|
||||
struct f_loopback {
|
||||
struct usb_function function;
|
||||
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
};
|
||||
|
||||
static inline struct f_loopback *func_to_loop(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_loopback, function);
|
||||
}
|
||||
|
||||
static unsigned qlen = 32;
|
||||
module_param(qlen, uint, 0);
|
||||
MODULE_PARM_DESC(qlenn, "depth of loopback queue");
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct usb_interface_descriptor loopback_intf = {
|
||||
.bLength = sizeof loopback_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_source_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_sink_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *fs_loopback_descs[] = {
|
||||
(struct usb_descriptor_header *) &loopback_intf,
|
||||
(struct usb_descriptor_header *) &fs_sink_desc,
|
||||
(struct usb_descriptor_header *) &fs_source_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_source_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_sink_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hs_loopback_descs[] = {
|
||||
(struct usb_descriptor_header *) &loopback_intf,
|
||||
(struct usb_descriptor_header *) &hs_source_desc,
|
||||
(struct usb_descriptor_header *) &hs_sink_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* function-specific strings: */
|
||||
|
||||
static struct usb_string strings_loopback[] = {
|
||||
[0].s = "loop input to output",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings stringtab_loop = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = strings_loopback,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *loopback_strings[] = {
|
||||
&stringtab_loop,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __init
|
||||
loopback_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_loopback *loop = func_to_loop(f);
|
||||
int id;
|
||||
|
||||
/* allocate interface ID(s) */
|
||||
id = usb_interface_id(c, f);
|
||||
if (id < 0)
|
||||
return id;
|
||||
loopback_intf.bInterfaceNumber = id;
|
||||
|
||||
/* allocate endpoints */
|
||||
|
||||
loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);
|
||||
if (!loop->in_ep) {
|
||||
autoconf_fail:
|
||||
ERROR(cdev, "%s: can't autoconfigure on %s\n",
|
||||
f->name, cdev->gadget->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
loop->in_ep->driver_data = cdev; /* claim */
|
||||
|
||||
loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc);
|
||||
if (!loop->out_ep)
|
||||
goto autoconf_fail;
|
||||
loop->out_ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* support high speed hardware */
|
||||
if (gadget_is_dualspeed(c->cdev->gadget)) {
|
||||
hs_source_desc.bEndpointAddress =
|
||||
fs_source_desc.bEndpointAddress;
|
||||
hs_sink_desc.bEndpointAddress =
|
||||
fs_sink_desc.bEndpointAddress;
|
||||
f->hs_descriptors = hs_loopback_descs;
|
||||
}
|
||||
|
||||
DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
f->name, loop->in_ep->name, loop->out_ep->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
loopback_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
kfree(func_to_loop(f));
|
||||
}
|
||||
|
||||
static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_loopback *loop = ep->driver_data;
|
||||
struct usb_composite_dev *cdev = loop->function.config->cdev;
|
||||
int status = req->status;
|
||||
|
||||
switch (status) {
|
||||
|
||||
case 0: /* normal completion? */
|
||||
if (ep == loop->out_ep) {
|
||||
/* loop this OUT packet back IN to the host */
|
||||
req->zero = (req->actual < req->length);
|
||||
req->length = req->actual;
|
||||
status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC);
|
||||
if (status == 0)
|
||||
return;
|
||||
|
||||
/* "should never get here" */
|
||||
ERROR(cdev, "can't loop %s to %s: %d\n",
|
||||
ep->name, loop->in_ep->name,
|
||||
status);
|
||||
}
|
||||
|
||||
/* queue the buffer for some later OUT packet */
|
||||
req->length = buflen;
|
||||
status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC);
|
||||
if (status == 0)
|
||||
return;
|
||||
|
||||
/* "should never get here" */
|
||||
/* FALLTHROUGH */
|
||||
|
||||
default:
|
||||
ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
|
||||
status, req->actual, req->length);
|
||||
/* FALLTHROUGH */
|
||||
|
||||
/* NOTE: since this driver doesn't maintain an explicit record
|
||||
* of requests it submitted (just maintains qlen count), we
|
||||
* rely on the hardware driver to clean up on disconnect or
|
||||
* endpoint disable.
|
||||
*/
|
||||
case -ECONNABORTED: /* hardware forced ep reset */
|
||||
case -ECONNRESET: /* request dequeued */
|
||||
case -ESHUTDOWN: /* disconnect from host */
|
||||
free_ep_req(ep, req);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void disable_loopback(struct f_loopback *loop)
|
||||
{
|
||||
struct usb_composite_dev *cdev;
|
||||
|
||||
cdev = loop->function.config->cdev;
|
||||
disable_endpoints(cdev, loop->in_ep, loop->out_ep);
|
||||
VDBG(cdev, "%s disabled\n", loop->function.name);
|
||||
}
|
||||
|
||||
static int
|
||||
enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)
|
||||
{
|
||||
int result = 0;
|
||||
const struct usb_endpoint_descriptor *src, *sink;
|
||||
struct usb_ep *ep;
|
||||
struct usb_request *req;
|
||||
unsigned i;
|
||||
|
||||
src = ep_choose(cdev->gadget, &hs_source_desc, &fs_source_desc);
|
||||
sink = ep_choose(cdev->gadget, &hs_sink_desc, &fs_sink_desc);
|
||||
|
||||
/* one endpoint writes data back IN to the host */
|
||||
ep = loop->in_ep;
|
||||
result = usb_ep_enable(ep, src);
|
||||
if (result < 0)
|
||||
return result;
|
||||
ep->driver_data = loop;
|
||||
|
||||
/* one endpoint just reads OUT packets */
|
||||
ep = loop->out_ep;
|
||||
result = usb_ep_enable(ep, sink);
|
||||
if (result < 0) {
|
||||
fail0:
|
||||
ep = loop->in_ep;
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
return result;
|
||||
}
|
||||
ep->driver_data = loop;
|
||||
|
||||
/* allocate a bunch of read buffers and queue them all at once.
|
||||
* we buffer at most 'qlen' transfers; fewer if any need more
|
||||
* than 'buflen' bytes each.
|
||||
*/
|
||||
for (i = 0; i < qlen && result == 0; i++) {
|
||||
req = alloc_ep_req(ep);
|
||||
if (req) {
|
||||
req->complete = loopback_complete;
|
||||
result = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
if (result)
|
||||
ERROR(cdev, "%s queue req --> %d\n",
|
||||
ep->name, result);
|
||||
} else {
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
result = -ENOMEM;
|
||||
goto fail0;
|
||||
}
|
||||
}
|
||||
|
||||
DBG(cdev, "%s enabled\n", loop->function.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int loopback_set_alt(struct usb_function *f,
|
||||
unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_loopback *loop = func_to_loop(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt is zero */
|
||||
if (loop->in_ep->driver_data)
|
||||
disable_loopback(loop);
|
||||
return enable_loopback(cdev, loop);
|
||||
}
|
||||
|
||||
static void loopback_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_loopback *loop = func_to_loop(f);
|
||||
|
||||
disable_loopback(loop);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __init loopback_bind_config(struct usb_configuration *c)
|
||||
{
|
||||
struct f_loopback *loop;
|
||||
int status;
|
||||
|
||||
loop = kzalloc(sizeof *loop, GFP_KERNEL);
|
||||
if (!loop)
|
||||
return -ENOMEM;
|
||||
|
||||
loop->function.name = "loopback";
|
||||
loop->function.descriptors = fs_loopback_descs;
|
||||
loop->function.bind = loopback_bind;
|
||||
loop->function.unbind = loopback_unbind;
|
||||
loop->function.set_alt = loopback_set_alt;
|
||||
loop->function.disable = loopback_disable;
|
||||
|
||||
status = usb_add_function(c, &loop->function);
|
||||
if (status)
|
||||
kfree(loop);
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct usb_configuration loopback_driver = {
|
||||
.label = "loopback",
|
||||
.strings = loopback_strings,
|
||||
.bind = loopback_bind_config,
|
||||
.bConfigurationValue = 2,
|
||||
.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
|
||||
.bMaxPower = 1, /* 2 mA, minimal */
|
||||
/* .iConfiguration = DYNAMIC */
|
||||
};
|
||||
|
||||
/**
|
||||
* loopback_add - add a loopback testing configuration to a device
|
||||
* @cdev: the device to support the loopback configuration
|
||||
*/
|
||||
int __init loopback_add(struct usb_composite_dev *cdev)
|
||||
{
|
||||
int id;
|
||||
|
||||
/* allocate string ID(s) */
|
||||
id = usb_string_id(cdev);
|
||||
if (id < 0)
|
||||
return id;
|
||||
strings_loopback[0].id = id;
|
||||
|
||||
loopback_intf.iInterface = id;
|
||||
loopback_driver.iConfiguration = id;
|
||||
|
||||
/* support OTG systems */
|
||||
if (gadget_is_otg(cdev->gadget)) {
|
||||
loopback_driver.descriptors = otg_desc;
|
||||
loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
|
||||
}
|
||||
|
||||
return usb_add_config(cdev, &loopback_driver);
|
||||
}
|
827
drivers/usb/gadget/f_rndis.c
Normal file
827
drivers/usb/gadget/f_rndis.c
Normal file
@ -0,0 +1,827 @@
|
||||
/*
|
||||
* f_rndis.c -- RNDIS link function driver
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include <asm/atomic.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
#include "rndis.h"
|
||||
|
||||
|
||||
/*
|
||||
* This function is an RNDIS Ethernet port -- a Microsoft protocol that's
|
||||
* been promoted instead of the standard CDC Ethernet. The published RNDIS
|
||||
* spec is ambiguous, incomplete, and needlessly complex. Variants such as
|
||||
* ActiveSync have even worse status in terms of specification.
|
||||
*
|
||||
* In short: it's a protocol controlled by (and for) Microsoft, not for an
|
||||
* Open ecosystem or markets. Linux supports it *only* because Microsoft
|
||||
* doesn't support the CDC Ethernet standard.
|
||||
*
|
||||
* The RNDIS data transfer model is complex, with multiple Ethernet packets
|
||||
* per USB message, and out of band data. The control model is built around
|
||||
* what's essentially an "RNDIS RPC" protocol. It's all wrapped in a CDC ACM
|
||||
* (modem, not Ethernet) veneer, with those ACM descriptors being entirely
|
||||
* useless (they're ignored). RNDIS expects to be the only function in its
|
||||
* configuration, so it's no real help if you need composite devices; and
|
||||
* it expects to be the first configuration too.
|
||||
*
|
||||
* There is a single technical advantage of RNDIS over CDC Ethernet, if you
|
||||
* discount the fluff that its RPC can be made to deliver: it doesn't need
|
||||
* a NOP altsetting for the data interface. That lets it work on some of the
|
||||
* "so smart it's stupid" hardware which takes over configuration changes
|
||||
* from the software, and adds restrictions like "no altsettings".
|
||||
*
|
||||
* Unfortunately MSFT's RNDIS drivers are buggy. They hang or oops, and
|
||||
* have all sorts of contrary-to-specification oddities that can prevent
|
||||
* them from working sanely. Since bugfixes (or accurate specs, letting
|
||||
* Linux work around those bugs) are unlikely to ever come from MSFT, you
|
||||
* may want to avoid using RNDIS on purely operational grounds.
|
||||
*
|
||||
* Omissions from the RNDIS 1.0 specification include:
|
||||
*
|
||||
* - Power management ... references data that's scattered around lots
|
||||
* of other documentation, which is incorrect/incomplete there too.
|
||||
*
|
||||
* - There are various undocumented protocol requirements, like the need
|
||||
* to send garbage in some control-OUT messages.
|
||||
*
|
||||
* - MS-Windows drivers sometimes emit undocumented requests.
|
||||
*/
|
||||
|
||||
struct rndis_ep_descs {
|
||||
struct usb_endpoint_descriptor *in;
|
||||
struct usb_endpoint_descriptor *out;
|
||||
struct usb_endpoint_descriptor *notify;
|
||||
};
|
||||
|
||||
struct f_rndis {
|
||||
struct gether port;
|
||||
u8 ctrl_id, data_id;
|
||||
u8 ethaddr[ETH_ALEN];
|
||||
int config;
|
||||
|
||||
struct usb_descriptor_header **fs_function;
|
||||
struct rndis_ep_descs fs;
|
||||
struct usb_descriptor_header **hs_function;
|
||||
struct rndis_ep_descs hs;
|
||||
|
||||
struct usb_ep *notify;
|
||||
struct usb_endpoint_descriptor *notify_desc;
|
||||
struct usb_request *notify_req;
|
||||
atomic_t notify_count;
|
||||
};
|
||||
|
||||
static inline struct f_rndis *func_to_rndis(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_rndis, port.func);
|
||||
}
|
||||
|
||||
/* peak (theoretical) bulk transfer rate in bits-per-second */
|
||||
static unsigned int bitrate(struct usb_gadget *g)
|
||||
{
|
||||
if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
|
||||
return 13 * 512 * 8 * 1000 * 8;
|
||||
else
|
||||
return 19 * 64 * 1 * 1000 * 8;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */
|
||||
#define STATUS_BYTECOUNT 8 /* 8 bytes data */
|
||||
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor rndis_control_intf __initdata = {
|
||||
.bLength = sizeof rndis_control_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
/* status endpoint is optional; this could be patched later */
|
||||
.bNumEndpoints = 1,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
|
||||
.bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc header_desc __initdata = {
|
||||
.bLength = sizeof header_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
|
||||
.bcdCDC = __constant_cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor __initdata = {
|
||||
.bLength = sizeof call_mgmt_descriptor,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
|
||||
|
||||
.bmCapabilities = 0x00,
|
||||
.bDataInterface = 0x01,
|
||||
};
|
||||
|
||||
static struct usb_cdc_acm_descriptor acm_descriptor __initdata = {
|
||||
.bLength = sizeof acm_descriptor,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ACM_TYPE,
|
||||
|
||||
.bmCapabilities = 0x00,
|
||||
};
|
||||
|
||||
static struct usb_cdc_union_desc rndis_union_desc __initdata = {
|
||||
.bLength = sizeof(rndis_union_desc),
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_UNION_TYPE,
|
||||
/* .bMasterInterface0 = DYNAMIC */
|
||||
/* .bSlaveInterface0 = DYNAMIC */
|
||||
};
|
||||
|
||||
/* the data interface has two bulk endpoints */
|
||||
|
||||
static struct usb_interface_descriptor rndis_data_intf __initdata = {
|
||||
.bLength = sizeof rndis_data_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_notify_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT),
|
||||
.bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eth_fs_function[] __initdata = {
|
||||
/* control interface matches ACM, not Ethernet */
|
||||
(struct usb_descriptor_header *) &rndis_control_intf,
|
||||
(struct usb_descriptor_header *) &header_desc,
|
||||
(struct usb_descriptor_header *) &call_mgmt_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_descriptor,
|
||||
(struct usb_descriptor_header *) &rndis_union_desc,
|
||||
(struct usb_descriptor_header *) &fs_notify_desc,
|
||||
/* data interface has no altsetting */
|
||||
(struct usb_descriptor_header *) &rndis_data_intf,
|
||||
(struct usb_descriptor_header *) &fs_in_desc,
|
||||
(struct usb_descriptor_header *) &fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_notify_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT),
|
||||
.bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
|
||||
};
|
||||
static struct usb_endpoint_descriptor hs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *eth_hs_function[] __initdata = {
|
||||
/* control interface matches ACM, not Ethernet */
|
||||
(struct usb_descriptor_header *) &rndis_control_intf,
|
||||
(struct usb_descriptor_header *) &header_desc,
|
||||
(struct usb_descriptor_header *) &call_mgmt_descriptor,
|
||||
(struct usb_descriptor_header *) &acm_descriptor,
|
||||
(struct usb_descriptor_header *) &rndis_union_desc,
|
||||
(struct usb_descriptor_header *) &hs_notify_desc,
|
||||
/* data interface has no altsetting */
|
||||
(struct usb_descriptor_header *) &rndis_data_intf,
|
||||
(struct usb_descriptor_header *) &hs_in_desc,
|
||||
(struct usb_descriptor_header *) &hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string rndis_string_defs[] = {
|
||||
[0].s = "RNDIS Communications Control",
|
||||
[1].s = "RNDIS Ethernet Data",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings rndis_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = rndis_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *rndis_strings[] = {
|
||||
&rndis_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct sk_buff *rndis_add_header(struct sk_buff *skb)
|
||||
{
|
||||
skb = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type));
|
||||
if (skb)
|
||||
rndis_add_hdr(skb);
|
||||
return skb;
|
||||
}
|
||||
|
||||
static void rndis_response_available(void *_rndis)
|
||||
{
|
||||
struct f_rndis *rndis = _rndis;
|
||||
struct usb_request *req = rndis->notify_req;
|
||||
struct usb_composite_dev *cdev = rndis->port.func.config->cdev;
|
||||
__le32 *data = req->buf;
|
||||
int status;
|
||||
|
||||
if (atomic_inc_return(&rndis->notify_count))
|
||||
return;
|
||||
|
||||
/* Send RNDIS RESPONSE_AVAILABLE notification; a
|
||||
* USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too
|
||||
*
|
||||
* This is the only notification defined by RNDIS.
|
||||
*/
|
||||
data[0] = cpu_to_le32(1);
|
||||
data[1] = cpu_to_le32(0);
|
||||
|
||||
status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
|
||||
if (status) {
|
||||
atomic_dec(&rndis->notify_count);
|
||||
DBG(cdev, "notify/0 --> %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
static void rndis_response_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 = req->status;
|
||||
|
||||
/* after TX:
|
||||
* - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control)
|
||||
* - RNDIS_RESPONSE_AVAILABLE (status/irq)
|
||||
*/
|
||||
switch (status) {
|
||||
case -ECONNRESET:
|
||||
case -ESHUTDOWN:
|
||||
/* connection gone */
|
||||
atomic_set(&rndis->notify_count, 0);
|
||||
break;
|
||||
default:
|
||||
DBG(cdev, "RNDIS %s response error %d, %d/%d\n",
|
||||
ep->name, status,
|
||||
req->actual, req->length);
|
||||
/* FALLTHROUGH */
|
||||
case 0:
|
||||
if (ep != rndis->notify)
|
||||
break;
|
||||
|
||||
/* handle multiple pending RNDIS_RESPONSE_AVAILABLE
|
||||
* notifications by resending until we're done
|
||||
*/
|
||||
if (atomic_dec_and_test(&rndis->notify_count))
|
||||
break;
|
||||
status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
|
||||
if (status) {
|
||||
atomic_dec(&rndis->notify_count);
|
||||
DBG(cdev, "notify/1 --> %d\n", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
status, req->actual, req->length);
|
||||
// spin_unlock(&dev->lock);
|
||||
}
|
||||
|
||||
static int
|
||||
rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct f_rndis *rndis = func_to_rndis(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything except
|
||||
* CDC class messages; interface activation uses set_alt().
|
||||
*/
|
||||
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||||
|
||||
/* RNDIS uses the CDC command encapsulation mechanism to implement
|
||||
* an RPC scheme, with much getting/setting of attributes by OID.
|
||||
*/
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_SEND_ENCAPSULATED_COMMAND:
|
||||
if (w_length > req->length || w_value
|
||||
|| w_index != rndis->ctrl_id)
|
||||
goto invalid;
|
||||
/* read the request; process it later */
|
||||
value = w_length;
|
||||
req->complete = rndis_command_complete;
|
||||
req->context = rndis;
|
||||
/* later, rndis_response_available() sends a notification */
|
||||
break;
|
||||
|
||||
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
|
||||
| USB_CDC_GET_ENCAPSULATED_RESPONSE:
|
||||
if (w_value || w_index != rndis->ctrl_id)
|
||||
goto invalid;
|
||||
else {
|
||||
u8 *buf;
|
||||
u32 n;
|
||||
|
||||
/* return the result */
|
||||
buf = rndis_get_next_response(rndis->config, &n);
|
||||
if (buf) {
|
||||
memcpy(req->buf, buf, n);
|
||||
req->complete = rndis_response_complete;
|
||||
rndis_free_response(rndis->config, buf);
|
||||
value = n;
|
||||
}
|
||||
/* else stalls ... spec says to avoid that */
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(cdev, "rndis response on err %d\n", value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_rndis *rndis = func_to_rndis(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt == 0 */
|
||||
|
||||
if (intf == rndis->ctrl_id) {
|
||||
if (rndis->notify->driver_data) {
|
||||
VDBG(cdev, "reset rndis control %d\n", intf);
|
||||
usb_ep_disable(rndis->notify);
|
||||
} else {
|
||||
VDBG(cdev, "init rndis ctrl %d\n", intf);
|
||||
rndis->notify_desc = ep_choose(cdev->gadget,
|
||||
rndis->hs.notify,
|
||||
rndis->fs.notify);
|
||||
}
|
||||
usb_ep_enable(rndis->notify, rndis->notify_desc);
|
||||
rndis->notify->driver_data = rndis;
|
||||
|
||||
} else if (intf == rndis->data_id) {
|
||||
struct net_device *net;
|
||||
|
||||
if (rndis->port.in_ep->driver_data) {
|
||||
DBG(cdev, "reset rndis\n");
|
||||
gether_disconnect(&rndis->port);
|
||||
} else {
|
||||
DBG(cdev, "init rndis\n");
|
||||
rndis->port.in = ep_choose(cdev->gadget,
|
||||
rndis->hs.in, rndis->fs.in);
|
||||
rndis->port.out = ep_choose(cdev->gadget,
|
||||
rndis->hs.out, rndis->fs.out);
|
||||
}
|
||||
|
||||
/* Avoid ZLPs; they can be troublesome. */
|
||||
rndis->port.is_zlp_ok = false;
|
||||
|
||||
/* RNDIS should be in the "RNDIS uninitialized" state,
|
||||
* either never activated or after rndis_uninit().
|
||||
*
|
||||
* We don't want data to flow here until a nonzero packet
|
||||
* filter is set, at which point it enters "RNDIS data
|
||||
* initialized" state ... but we do want the endpoints
|
||||
* to be activated. It's a strange little state.
|
||||
*
|
||||
* REVISIT the RNDIS gadget code has done this wrong for a
|
||||
* very long time. We need another call to the link layer
|
||||
* code -- gether_updown(...bool) maybe -- to do it right.
|
||||
*/
|
||||
rndis->port.cdc_filter = 0;
|
||||
|
||||
DBG(cdev, "RNDIS RX/TX early activation ... \n");
|
||||
net = gether_connect(&rndis->port);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
|
||||
rndis_set_param_dev(rndis->config, net,
|
||||
&rndis->port.cdc_filter);
|
||||
} else
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void rndis_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_rndis *rndis = func_to_rndis(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
if (!rndis->notify->driver_data)
|
||||
return;
|
||||
|
||||
DBG(cdev, "rndis deactivated\n");
|
||||
|
||||
rndis_uninit(rndis->config);
|
||||
gether_disconnect(&rndis->port);
|
||||
|
||||
usb_ep_disable(rndis->notify);
|
||||
rndis->notify->driver_data = NULL;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* This isn't quite the same mechanism as CDC Ethernet, since the
|
||||
* notification scheme passes less data, but the same set of link
|
||||
* states must be tested. A key difference is that altsettings are
|
||||
* not used to tell whether the link should send packets or not.
|
||||
*/
|
||||
|
||||
static void rndis_open(struct gether *geth)
|
||||
{
|
||||
struct f_rndis *rndis = func_to_rndis(&geth->func);
|
||||
struct usb_composite_dev *cdev = geth->func.config->cdev;
|
||||
|
||||
DBG(cdev, "%s\n", __func__);
|
||||
|
||||
rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3,
|
||||
bitrate(cdev->gadget) / 100);
|
||||
rndis_signal_connect(rndis->config);
|
||||
}
|
||||
|
||||
static void rndis_close(struct gether *geth)
|
||||
{
|
||||
struct f_rndis *rndis = func_to_rndis(&geth->func);
|
||||
|
||||
DBG(geth->func.config->cdev, "%s\n", __func__);
|
||||
|
||||
rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0);
|
||||
rndis_signal_disconnect(rndis->config);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* ethernet function driver setup/binding */
|
||||
|
||||
static int __init
|
||||
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);
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
rndis->ctrl_id = status;
|
||||
|
||||
rndis_control_intf.bInterfaceNumber = status;
|
||||
rndis_union_desc.bMasterInterface0 = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
rndis->data_id = status;
|
||||
|
||||
rndis_data_intf.bInterfaceNumber = status;
|
||||
rndis_union_desc.bSlaveInterface0 = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
rndis->port.in_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
rndis->port.out_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* NOTE: a status/notification endpoint is, strictly speaking,
|
||||
* optional. We don't treat it that way though! It's simpler,
|
||||
* and some newer profiles don't treat it as optional.
|
||||
*/
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
rndis->notify = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
status = -ENOMEM;
|
||||
|
||||
/* allocate notification request and buffer */
|
||||
rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
|
||||
if (!rndis->notify_req)
|
||||
goto fail;
|
||||
rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
|
||||
if (!rndis->notify_req->buf)
|
||||
goto fail;
|
||||
rndis->notify_req->length = STATUS_BYTECOUNT;
|
||||
rndis->notify_req->context = rndis;
|
||||
rndis->notify_req->complete = rndis_response_complete;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->descriptors = usb_copy_descriptors(eth_fs_function);
|
||||
if (!f->descriptors)
|
||||
goto fail;
|
||||
|
||||
rndis->fs.in = usb_find_endpoint(eth_fs_function,
|
||||
f->descriptors, &fs_in_desc);
|
||||
rndis->fs.out = usb_find_endpoint(eth_fs_function,
|
||||
f->descriptors, &fs_out_desc);
|
||||
rndis->fs.notify = usb_find_endpoint(eth_fs_function,
|
||||
f->descriptors, &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)) {
|
||||
hs_in_desc.bEndpointAddress =
|
||||
fs_in_desc.bEndpointAddress;
|
||||
hs_out_desc.bEndpointAddress =
|
||||
fs_out_desc.bEndpointAddress;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->hs_descriptors = usb_copy_descriptors(eth_hs_function);
|
||||
|
||||
if (!f->hs_descriptors)
|
||||
goto fail;
|
||||
|
||||
rndis->hs.in = usb_find_endpoint(eth_hs_function,
|
||||
f->hs_descriptors, &hs_in_desc);
|
||||
rndis->hs.out = usb_find_endpoint(eth_hs_function,
|
||||
f->hs_descriptors, &hs_out_desc);
|
||||
}
|
||||
|
||||
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, NDIS_MEDIUM_802_3, 0);
|
||||
rndis_set_host_mac(rndis->config, rndis->ethaddr);
|
||||
|
||||
#if 0
|
||||
// FIXME
|
||||
if (rndis_set_param_vendor(rndis->config, vendorID,
|
||||
manufacturer))
|
||||
goto fail0;
|
||||
#endif
|
||||
|
||||
/* NOTE: all that is done without knowing or caring about
|
||||
* the network link ... which is unavailable to this code
|
||||
* until we're activated via set_alt().
|
||||
*/
|
||||
|
||||
DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
rndis->port.in_ep->name, rndis->port.out_ep->name,
|
||||
rndis->notify->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors)
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
if (f->descriptors)
|
||||
usb_free_descriptors(f->descriptors);
|
||||
|
||||
if (rndis->notify_req) {
|
||||
kfree(rndis->notify_req->buf);
|
||||
usb_ep_free_request(rndis->notify, rndis->notify_req);
|
||||
}
|
||||
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (rndis->notify)
|
||||
rndis->notify->driver_data = NULL;
|
||||
if (rndis->port.out)
|
||||
rndis->port.out_ep->driver_data = NULL;
|
||||
if (rndis->port.in)
|
||||
rndis->port.in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
rndis_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct f_rndis *rndis = func_to_rndis(f);
|
||||
|
||||
rndis_deregister(rndis->config);
|
||||
rndis_exit();
|
||||
|
||||
if (gadget_is_dualspeed(c->cdev->gadget))
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
usb_free_descriptors(f->descriptors);
|
||||
|
||||
kfree(rndis->notify_req->buf);
|
||||
usb_ep_free_request(rndis->notify, rndis->notify_req);
|
||||
|
||||
kfree(rndis);
|
||||
}
|
||||
|
||||
/* Some controllers can't support RNDIS ... */
|
||||
static inline bool can_support_rndis(struct usb_configuration *c)
|
||||
{
|
||||
/* only two endpoints on sa1100 */
|
||||
if (gadget_is_sa1100(c->cdev->gadget))
|
||||
return false;
|
||||
|
||||
/* everything else is *presumably* fine */
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* rndis_bind_config - add RNDIS network link to a configuration
|
||||
* @c: the configuration to support the network link
|
||||
* @ethaddr: a buffer in which the ethernet address of the host side
|
||||
* side of the link was recorded
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*
|
||||
* Caller must have called @gether_setup(). Caller is also responsible
|
||||
* for calling @gether_cleanup() before module unload.
|
||||
*/
|
||||
int __init rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
|
||||
{
|
||||
struct f_rndis *rndis;
|
||||
int status;
|
||||
|
||||
if (!can_support_rndis(c) || !ethaddr)
|
||||
return -EINVAL;
|
||||
|
||||
/* maybe allocate device-global string IDs */
|
||||
if (rndis_string_defs[0].id == 0) {
|
||||
|
||||
/* ... and setup RNDIS itself */
|
||||
status = rndis_init();
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
/* control interface label */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
rndis_string_defs[0].id = status;
|
||||
rndis_control_intf.iInterface = status;
|
||||
|
||||
/* data interface label */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
rndis_string_defs[1].id = status;
|
||||
rndis_data_intf.iInterface = status;
|
||||
}
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
status = -ENOMEM;
|
||||
rndis = kzalloc(sizeof *rndis, GFP_KERNEL);
|
||||
if (!rndis)
|
||||
goto fail;
|
||||
|
||||
memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);
|
||||
|
||||
/* RNDIS activates when the host changes this filter */
|
||||
rndis->port.cdc_filter = 0;
|
||||
|
||||
/* RNDIS has special (and complex) framing */
|
||||
rndis->port.header_len = sizeof(struct rndis_packet_msg_type);
|
||||
rndis->port.wrap = rndis_add_header;
|
||||
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;
|
||||
|
||||
status = usb_add_function(c, &rndis->port.func);
|
||||
if (status) {
|
||||
kfree(rndis);
|
||||
fail:
|
||||
rndis_exit();
|
||||
}
|
||||
return status;
|
||||
}
|
296
drivers/usb/gadget/f_serial.c
Normal file
296
drivers/usb/gadget/f_serial.c
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* f_serial.c - generic USB serial function driver
|
||||
*
|
||||
* Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
|
||||
* Copyright (C) 2008 by David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "u_serial.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This function packages a simple "generic serial" port with no real
|
||||
* control mechanisms, just raw data transfer over two bulk endpoints.
|
||||
*
|
||||
* Because it's not standardized, this isn't as interoperable as the
|
||||
* CDC ACM driver. However, for many purposes it's just as functional
|
||||
* if you can arrange appropriate host side drivers.
|
||||
*/
|
||||
|
||||
struct gser_descs {
|
||||
struct usb_endpoint_descriptor *in;
|
||||
struct usb_endpoint_descriptor *out;
|
||||
};
|
||||
|
||||
struct f_gser {
|
||||
struct gserial port;
|
||||
u8 data_id;
|
||||
u8 port_num;
|
||||
|
||||
struct usb_descriptor_header **fs_function;
|
||||
struct gser_descs fs;
|
||||
struct usb_descriptor_header **hs_function;
|
||||
struct gser_descs hs;
|
||||
};
|
||||
|
||||
static inline struct f_gser *func_to_gser(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_gser, port.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor gser_interface_desc __initdata = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||
.bInterfaceSubClass = 0,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor gser_fs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor gser_fs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *gser_fs_function[] __initdata = {
|
||||
(struct usb_descriptor_header *) &gser_interface_desc,
|
||||
(struct usb_descriptor_header *) &gser_fs_in_desc,
|
||||
(struct usb_descriptor_header *) &gser_fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor gser_hs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor gser_hs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *gser_hs_function[] __initdata = {
|
||||
(struct usb_descriptor_header *) &gser_interface_desc,
|
||||
(struct usb_descriptor_header *) &gser_hs_in_desc,
|
||||
(struct usb_descriptor_header *) &gser_hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string gser_string_defs[] = {
|
||||
[0].s = "Generic Serial",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings gser_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = gser_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *gser_strings[] = {
|
||||
&gser_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_gser *gser = func_to_gser(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
|
||||
if (gser->port.in->driver_data) {
|
||||
DBG(cdev, "reset generic ttyGS%d\n", gser->port_num);
|
||||
gserial_disconnect(&gser->port);
|
||||
} else {
|
||||
DBG(cdev, "activate generic ttyGS%d\n", gser->port_num);
|
||||
gser->port.in_desc = ep_choose(cdev->gadget,
|
||||
gser->hs.in, gser->fs.in);
|
||||
gser->port.out_desc = ep_choose(cdev->gadget,
|
||||
gser->hs.out, gser->fs.out);
|
||||
}
|
||||
gserial_connect(&gser->port, gser->port_num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gser_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_gser *gser = func_to_gser(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num);
|
||||
gserial_disconnect(&gser->port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* serial function driver setup/binding */
|
||||
|
||||
static int __init
|
||||
gser_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_gser *gser = func_to_gser(f);
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
gser->data_id = status;
|
||||
gser_interface_desc.bInterfaceNumber = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
gser->port.in = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
gser->port.out = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->descriptors = usb_copy_descriptors(gser_fs_function);
|
||||
|
||||
gser->fs.in = usb_find_endpoint(gser_fs_function,
|
||||
f->descriptors, &gser_fs_in_desc);
|
||||
gser->fs.out = usb_find_endpoint(gser_fs_function,
|
||||
f->descriptors, &gser_fs_out_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)) {
|
||||
gser_hs_in_desc.bEndpointAddress =
|
||||
gser_fs_in_desc.bEndpointAddress;
|
||||
gser_hs_out_desc.bEndpointAddress =
|
||||
gser_fs_out_desc.bEndpointAddress;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->hs_descriptors = usb_copy_descriptors(gser_hs_function);
|
||||
|
||||
gser->hs.in = usb_find_endpoint(gser_hs_function,
|
||||
f->hs_descriptors, &gser_hs_in_desc);
|
||||
gser->hs.out = usb_find_endpoint(gser_hs_function,
|
||||
f->hs_descriptors, &gser_hs_out_desc);
|
||||
}
|
||||
|
||||
DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
gser->port_num,
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
gser->port.in->name, gser->port.out->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (gser->port.out)
|
||||
gser->port.out->driver_data = NULL;
|
||||
if (gser->port.in)
|
||||
gser->port.in->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
gser_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
if (gadget_is_dualspeed(c->cdev->gadget))
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
usb_free_descriptors(f->descriptors);
|
||||
kfree(func_to_gser(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* gser_bind_config - add a generic serial function to a configuration
|
||||
* @c: the configuration to support the serial 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 __init gser_bind_config(struct usb_configuration *c, u8 port_num)
|
||||
{
|
||||
struct f_gser *gser;
|
||||
int status;
|
||||
|
||||
/* REVISIT might want instance-specific strings to help
|
||||
* distinguish instances ...
|
||||
*/
|
||||
|
||||
/* maybe allocate device-global string ID */
|
||||
if (gser_string_defs[0].id == 0) {
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
gser_string_defs[0].id = status;
|
||||
}
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
gser = kzalloc(sizeof *gser, GFP_KERNEL);
|
||||
if (!gser)
|
||||
return -ENOMEM;
|
||||
|
||||
gser->port_num = port_num;
|
||||
|
||||
gser->port.func.name = "gser";
|
||||
gser->port.func.strings = gser_strings;
|
||||
gser->port.func.bind = gser_bind;
|
||||
gser->port.func.unbind = gser_unbind;
|
||||
gser->port.func.set_alt = gser_set_alt;
|
||||
gser->port.func.disable = gser_disable;
|
||||
|
||||
status = usb_add_function(c, &gser->port.func);
|
||||
if (status)
|
||||
kfree(gser);
|
||||
return status;
|
||||
}
|
587
drivers/usb/gadget/f_sourcesink.c
Normal file
587
drivers/usb/gadget/f_sourcesink.c
Normal file
@ -0,0 +1,587 @@
|
||||
/*
|
||||
* f_sourcesink.c - USB peripheral source/sink configuration driver
|
||||
*
|
||||
* Copyright (C) 2003-2008 David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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/utsname.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "g_zero.h"
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral
|
||||
* controller drivers.
|
||||
*
|
||||
* This just sinks bulk packets OUT to the peripheral and sources them IN
|
||||
* to the host, optionally with specific data patterns for integrity tests.
|
||||
* As such it supports basic functionality and load tests.
|
||||
*
|
||||
* In terms of control messaging, this supports all the standard requests
|
||||
* plus two that support control-OUT tests. If the optional "autoresume"
|
||||
* mode is enabled, it provides good functional coverage for the "USBCV"
|
||||
* test harness from USB-IF.
|
||||
*
|
||||
* Note that because this doesn't queue more than one request at a time,
|
||||
* some other function must be used to test queueing logic. The network
|
||||
* link (g_ether) is the best overall option for that, since its TX and RX
|
||||
* queues are relatively independent, will receive a range of packet sizes,
|
||||
* and can often be made to run out completely. Those issues are important
|
||||
* when stress testing peripheral controller drivers.
|
||||
*
|
||||
*
|
||||
* This is currently packaged as a configuration driver, which can't be
|
||||
* combined with other functions to make composite devices. However, it
|
||||
* can be combined with other independent configurations.
|
||||
*/
|
||||
struct f_sourcesink {
|
||||
struct usb_function function;
|
||||
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
struct timer_list resume;
|
||||
};
|
||||
|
||||
static inline struct f_sourcesink *func_to_ss(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_sourcesink, function);
|
||||
}
|
||||
|
||||
static unsigned autoresume;
|
||||
module_param(autoresume, uint, 0);
|
||||
MODULE_PARM_DESC(autoresume, "zero, or seconds before remote wakeup");
|
||||
|
||||
static unsigned pattern;
|
||||
module_param(pattern, uint, 0);
|
||||
MODULE_PARM_DESC(pattern, "0 = all zeroes, 1 = mod63 ");
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct usb_interface_descriptor source_sink_intf = {
|
||||
.bLength = sizeof source_sink_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_source_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_sink_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *fs_source_sink_descs[] = {
|
||||
(struct usb_descriptor_header *) &source_sink_intf,
|
||||
(struct usb_descriptor_header *) &fs_sink_desc,
|
||||
(struct usb_descriptor_header *) &fs_source_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_source_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_sink_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hs_source_sink_descs[] = {
|
||||
(struct usb_descriptor_header *) &source_sink_intf,
|
||||
(struct usb_descriptor_header *) &hs_source_desc,
|
||||
(struct usb_descriptor_header *) &hs_sink_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* function-specific strings: */
|
||||
|
||||
static struct usb_string strings_sourcesink[] = {
|
||||
[0].s = "source and sink data",
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings stringtab_sourcesink = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = strings_sourcesink,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *sourcesink_strings[] = {
|
||||
&stringtab_sourcesink,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void sourcesink_autoresume(unsigned long _c)
|
||||
{
|
||||
struct usb_composite_dev *cdev = (void *)_c;
|
||||
struct usb_gadget *g = cdev->gadget;
|
||||
|
||||
/* Normally the host would be woken up for something
|
||||
* more significant than just a timer firing; likely
|
||||
* because of some direct user request.
|
||||
*/
|
||||
if (g->speed != USB_SPEED_UNKNOWN) {
|
||||
int status = usb_gadget_wakeup(g);
|
||||
DBG(cdev, "%s --> %d\n", __func__, status);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init
|
||||
sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_sourcesink *ss = func_to_ss(f);
|
||||
int id;
|
||||
|
||||
/* allocate interface ID(s) */
|
||||
id = usb_interface_id(c, f);
|
||||
if (id < 0)
|
||||
return id;
|
||||
source_sink_intf.bInterfaceNumber = id;
|
||||
|
||||
/* allocate endpoints */
|
||||
ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);
|
||||
if (!ss->in_ep) {
|
||||
autoconf_fail:
|
||||
ERROR(cdev, "%s: can't autoconfigure on %s\n",
|
||||
f->name, cdev->gadget->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
ss->in_ep->driver_data = cdev; /* claim */
|
||||
|
||||
ss->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc);
|
||||
if (!ss->out_ep)
|
||||
goto autoconf_fail;
|
||||
ss->out_ep->driver_data = cdev; /* claim */
|
||||
|
||||
setup_timer(&ss->resume, sourcesink_autoresume,
|
||||
(unsigned long) c->cdev);
|
||||
|
||||
/* support high speed hardware */
|
||||
if (gadget_is_dualspeed(c->cdev->gadget)) {
|
||||
hs_source_desc.bEndpointAddress =
|
||||
fs_source_desc.bEndpointAddress;
|
||||
hs_sink_desc.bEndpointAddress =
|
||||
fs_sink_desc.bEndpointAddress;
|
||||
f->hs_descriptors = hs_source_sink_descs;
|
||||
}
|
||||
|
||||
DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
f->name, ss->in_ep->name, ss->out_ep->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sourcesink_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
kfree(func_to_ss(f));
|
||||
}
|
||||
|
||||
/* optionally require specific source/sink data patterns */
|
||||
static int check_read_data(struct f_sourcesink *ss, struct usb_request *req)
|
||||
{
|
||||
unsigned i;
|
||||
u8 *buf = req->buf;
|
||||
struct usb_composite_dev *cdev = ss->function.config->cdev;
|
||||
|
||||
for (i = 0; i < req->actual; i++, buf++) {
|
||||
switch (pattern) {
|
||||
|
||||
/* all-zeroes has no synchronization issues */
|
||||
case 0:
|
||||
if (*buf == 0)
|
||||
continue;
|
||||
break;
|
||||
|
||||
/* "mod63" stays in sync with short-terminated transfers,
|
||||
* OR otherwise when host and gadget agree on how large
|
||||
* each usb transfer request should be. Resync is done
|
||||
* with set_interface or set_config. (We *WANT* it to
|
||||
* get quickly out of sync if controllers or their drivers
|
||||
* stutter for any reason, including buffer duplcation...)
|
||||
*/
|
||||
case 1:
|
||||
if (*buf == (u8)(i % 63))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
ERROR(cdev, "bad OUT byte, buf[%d] = %d\n", i, *buf);
|
||||
usb_ep_set_halt(ss->out_ep);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void reinit_write_data(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
unsigned i;
|
||||
u8 *buf = req->buf;
|
||||
|
||||
switch (pattern) {
|
||||
case 0:
|
||||
memset(req->buf, 0, req->length);
|
||||
break;
|
||||
case 1:
|
||||
for (i = 0; i < req->length; i++)
|
||||
*buf++ = (u8) (i % 63);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void source_sink_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct f_sourcesink *ss = ep->driver_data;
|
||||
struct usb_composite_dev *cdev = ss->function.config->cdev;
|
||||
int status = req->status;
|
||||
|
||||
switch (status) {
|
||||
|
||||
case 0: /* normal completion? */
|
||||
if (ep == ss->out_ep) {
|
||||
check_read_data(ss, req);
|
||||
memset(req->buf, 0x55, req->length);
|
||||
} else
|
||||
reinit_write_data(ep, req);
|
||||
break;
|
||||
|
||||
/* this endpoint is normally active while we're configured */
|
||||
case -ECONNABORTED: /* hardware forced ep reset */
|
||||
case -ECONNRESET: /* request dequeued */
|
||||
case -ESHUTDOWN: /* disconnect from host */
|
||||
VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status,
|
||||
req->actual, req->length);
|
||||
if (ep == ss->out_ep)
|
||||
check_read_data(ss, req);
|
||||
free_ep_req(ep, req);
|
||||
return;
|
||||
|
||||
case -EOVERFLOW: /* buffer overrun on read means that
|
||||
* we didn't provide a big enough
|
||||
* buffer.
|
||||
*/
|
||||
default:
|
||||
#if 1
|
||||
DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name,
|
||||
status, req->actual, req->length);
|
||||
#endif
|
||||
case -EREMOTEIO: /* short read */
|
||||
break;
|
||||
}
|
||||
|
||||
status = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
if (status) {
|
||||
ERROR(cdev, "kill %s: resubmit %d bytes --> %d\n",
|
||||
ep->name, req->length, status);
|
||||
usb_ep_set_halt(ep);
|
||||
/* FIXME recover later ... somehow */
|
||||
}
|
||||
}
|
||||
|
||||
static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in)
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
struct usb_request *req;
|
||||
int status;
|
||||
|
||||
ep = is_in ? ss->in_ep : ss->out_ep;
|
||||
req = alloc_ep_req(ep);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
req->complete = source_sink_complete;
|
||||
if (is_in)
|
||||
reinit_write_data(ep, req);
|
||||
else
|
||||
memset(req->buf, 0x55, req->length);
|
||||
|
||||
status = usb_ep_queue(ep, req, GFP_ATOMIC);
|
||||
if (status) {
|
||||
struct usb_composite_dev *cdev;
|
||||
|
||||
cdev = ss->function.config->cdev;
|
||||
ERROR(cdev, "start %s %s --> %d\n",
|
||||
is_in ? "IN" : "OUT",
|
||||
ep->name, status);
|
||||
free_ep_req(ep, req);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void disable_source_sink(struct f_sourcesink *ss)
|
||||
{
|
||||
struct usb_composite_dev *cdev;
|
||||
|
||||
cdev = ss->function.config->cdev;
|
||||
disable_endpoints(cdev, ss->in_ep, ss->out_ep);
|
||||
del_timer(&ss->resume);
|
||||
VDBG(cdev, "%s disabled\n", ss->function.name);
|
||||
}
|
||||
|
||||
static int
|
||||
enable_source_sink(struct usb_composite_dev *cdev, struct f_sourcesink *ss)
|
||||
{
|
||||
int result = 0;
|
||||
const struct usb_endpoint_descriptor *src, *sink;
|
||||
struct usb_ep *ep;
|
||||
|
||||
src = ep_choose(cdev->gadget, &hs_source_desc, &fs_source_desc);
|
||||
sink = ep_choose(cdev->gadget, &hs_sink_desc, &fs_sink_desc);
|
||||
|
||||
/* one endpoint writes (sources) zeroes IN (to the host) */
|
||||
ep = ss->in_ep;
|
||||
result = usb_ep_enable(ep, src);
|
||||
if (result < 0)
|
||||
return result;
|
||||
ep->driver_data = ss;
|
||||
|
||||
result = source_sink_start_ep(ss, true);
|
||||
if (result < 0) {
|
||||
fail:
|
||||
ep = ss->in_ep;
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* one endpoint reads (sinks) anything OUT (from the host) */
|
||||
ep = ss->out_ep;
|
||||
result = usb_ep_enable(ep, sink);
|
||||
if (result < 0)
|
||||
goto fail;
|
||||
ep->driver_data = ss;
|
||||
|
||||
result = source_sink_start_ep(ss, false);
|
||||
if (result < 0) {
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
DBG(cdev, "%s enabled\n", ss->function.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int sourcesink_set_alt(struct usb_function *f,
|
||||
unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_sourcesink *ss = func_to_ss(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
/* we know alt is zero */
|
||||
if (ss->in_ep->driver_data)
|
||||
disable_source_sink(ss);
|
||||
return enable_source_sink(cdev, ss);
|
||||
}
|
||||
|
||||
static void sourcesink_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_sourcesink *ss = func_to_ss(f);
|
||||
|
||||
disable_source_sink(ss);
|
||||
}
|
||||
|
||||
static void sourcesink_suspend(struct usb_function *f)
|
||||
{
|
||||
struct f_sourcesink *ss = func_to_ss(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
if (cdev->gadget->speed == USB_SPEED_UNKNOWN)
|
||||
return;
|
||||
|
||||
if (autoresume) {
|
||||
mod_timer(&ss->resume, jiffies + (HZ * autoresume));
|
||||
DBG(cdev, "suspend, wakeup in %d seconds\n", autoresume);
|
||||
} else
|
||||
DBG(cdev, "%s\n", __func__);
|
||||
}
|
||||
|
||||
static void sourcesink_resume(struct usb_function *f)
|
||||
{
|
||||
struct f_sourcesink *ss = func_to_ss(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "%s\n", __func__);
|
||||
del_timer(&ss->resume);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int __init sourcesink_bind_config(struct usb_configuration *c)
|
||||
{
|
||||
struct f_sourcesink *ss;
|
||||
int status;
|
||||
|
||||
ss = kzalloc(sizeof *ss, GFP_KERNEL);
|
||||
if (!ss)
|
||||
return -ENOMEM;
|
||||
|
||||
ss->function.name = "source/sink";
|
||||
ss->function.descriptors = fs_source_sink_descs;
|
||||
ss->function.bind = sourcesink_bind;
|
||||
ss->function.unbind = sourcesink_unbind;
|
||||
ss->function.set_alt = sourcesink_set_alt;
|
||||
ss->function.disable = sourcesink_disable;
|
||||
ss->function.suspend = sourcesink_suspend;
|
||||
ss->function.resume = sourcesink_resume;
|
||||
|
||||
status = usb_add_function(c, &ss->function);
|
||||
if (status)
|
||||
kfree(ss);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int sourcesink_setup(struct usb_configuration *c,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct usb_request *req = c->cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
/* composite driver infrastructure handles everything except
|
||||
* the two control test requests.
|
||||
*/
|
||||
switch (ctrl->bRequest) {
|
||||
|
||||
/*
|
||||
* These are the same vendor-specific requests supported by
|
||||
* Intel's USB 2.0 compliance test devices. We exceed that
|
||||
* device spec by allowing multiple-packet requests.
|
||||
*
|
||||
* NOTE: the Control-OUT data stays in req->buf ... better
|
||||
* would be copying it into a scratch buffer, so that other
|
||||
* requests may safely intervene.
|
||||
*/
|
||||
case 0x5b: /* control WRITE test -- fill the buffer */
|
||||
if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR))
|
||||
goto unknown;
|
||||
if (w_value || w_index)
|
||||
break;
|
||||
/* just read that many bytes into the buffer */
|
||||
if (w_length > req->length)
|
||||
break;
|
||||
value = w_length;
|
||||
break;
|
||||
case 0x5c: /* control READ test -- return the buffer */
|
||||
if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR))
|
||||
goto unknown;
|
||||
if (w_value || w_index)
|
||||
break;
|
||||
/* expect those bytes are still in the buffer; send back */
|
||||
if (w_length > req->length)
|
||||
break;
|
||||
value = w_length;
|
||||
break;
|
||||
|
||||
default:
|
||||
unknown:
|
||||
VDBG(c->cdev,
|
||||
"unknown control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
if (value >= 0) {
|
||||
VDBG(c->cdev, "source/sink req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
value = usb_ep_queue(c->cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
ERROR(c->cdev, "source/sinkc response, err %d\n",
|
||||
value);
|
||||
}
|
||||
|
||||
/* device either stalls (value < 0) or reports success */
|
||||
return value;
|
||||
}
|
||||
|
||||
static struct usb_configuration sourcesink_driver = {
|
||||
.label = "source/sink",
|
||||
.strings = sourcesink_strings,
|
||||
.bind = sourcesink_bind_config,
|
||||
.setup = sourcesink_setup,
|
||||
.bConfigurationValue = 3,
|
||||
.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
|
||||
.bMaxPower = 1, /* 2 mA, minimal */
|
||||
/* .iConfiguration = DYNAMIC */
|
||||
};
|
||||
|
||||
/**
|
||||
* sourcesink_add - add a source/sink testing configuration to a device
|
||||
* @cdev: the device to support the configuration
|
||||
*/
|
||||
int __init sourcesink_add(struct usb_composite_dev *cdev)
|
||||
{
|
||||
int id;
|
||||
|
||||
/* allocate string ID(s) */
|
||||
id = usb_string_id(cdev);
|
||||
if (id < 0)
|
||||
return id;
|
||||
strings_sourcesink[0].id = id;
|
||||
|
||||
source_sink_intf.iInterface = id;
|
||||
sourcesink_driver.iConfiguration = id;
|
||||
|
||||
/* support autoresume for remote wakeup testing */
|
||||
if (autoresume)
|
||||
sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
|
||||
|
||||
/* support OTG systems */
|
||||
if (gadget_is_otg(cdev->gadget)) {
|
||||
sourcesink_driver.descriptors = otg_desc;
|
||||
sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
|
||||
}
|
||||
|
||||
return usb_add_config(cdev, &sourcesink_driver);
|
||||
}
|
423
drivers/usb/gadget/f_subset.c
Normal file
423
drivers/usb/gadget/f_subset.c
Normal file
@ -0,0 +1,423 @@
|
||||
/*
|
||||
* f_subset.c -- "CDC Subset" Ethernet link function driver
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "u_ether.h"
|
||||
|
||||
|
||||
/*
|
||||
* This function packages a simple "CDC Subset" Ethernet port with no real
|
||||
* control mechanisms; just raw data transfer over two bulk endpoints.
|
||||
* The data transfer model is exactly that of CDC Ethernet, which is
|
||||
* why we call it the "CDC Subset".
|
||||
*
|
||||
* Because it's not standardized, this has some interoperability issues.
|
||||
* They mostly relate to driver binding, since the data transfer model is
|
||||
* so simple (CDC Ethernet). The original versions of this protocol used
|
||||
* specific product/vendor IDs: byteswapped IDs for Digital Equipment's
|
||||
* SA-1100 "Itsy" board, which could run Linux 2.4 kernels and supported
|
||||
* daughtercards with USB peripheral connectors. (It was used more often
|
||||
* with other boards, using the Itsy identifiers.) Linux hosts recognized
|
||||
* this with CONFIG_USB_ARMLINUX; these devices have only one configuration
|
||||
* and one interface.
|
||||
*
|
||||
* At some point, MCCI defined a (nonconformant) CDC MDLM variant called
|
||||
* "SAFE", which happens to have a mode which is identical to the "CDC
|
||||
* Subset" in terms of data transfer and lack of control model. This was
|
||||
* adopted by later Sharp Zaurus models, and by some other software which
|
||||
* Linux hosts recognize with CONFIG_USB_NET_ZAURUS.
|
||||
*
|
||||
* Because Microsoft's RNDIS drivers are far from robust, we added a few
|
||||
* descriptors to the CDC Subset code, making this code look like a SAFE
|
||||
* implementation. This lets you use MCCI's host side MS-Windows drivers
|
||||
* if you get fed up with RNDIS. It also makes it easier for composite
|
||||
* drivers to work, since they can use class based binding instead of
|
||||
* caring about specific product and vendor IDs.
|
||||
*/
|
||||
|
||||
struct geth_descs {
|
||||
struct usb_endpoint_descriptor *in;
|
||||
struct usb_endpoint_descriptor *out;
|
||||
};
|
||||
|
||||
struct f_gether {
|
||||
struct gether port;
|
||||
|
||||
char ethaddr[14];
|
||||
|
||||
struct usb_descriptor_header **fs_function;
|
||||
struct geth_descs fs;
|
||||
struct usb_descriptor_header **hs_function;
|
||||
struct geth_descs hs;
|
||||
};
|
||||
|
||||
static inline struct f_gether *func_to_geth(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct f_gether, port.func);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* "Simple" CDC-subset option is a simple vendor-neutral model that most
|
||||
* full speed controllers can handle: one interface, two bulk endpoints.
|
||||
* To assist host side drivers, we fancy it up a bit, and add descriptors so
|
||||
* some host side drivers will understand it as a "SAFE" variant.
|
||||
*
|
||||
* "SAFE" loosely follows CDC WMC MDLM, violating the spec in various ways.
|
||||
* Data endpoints live in the control interface, there's no data interface.
|
||||
* And it's not used to talk to a cell phone radio.
|
||||
*/
|
||||
|
||||
/* interface descriptor: */
|
||||
|
||||
static struct usb_interface_descriptor subset_data_intf __initdata = {
|
||||
.bLength = sizeof subset_data_intf,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
|
||||
/* .bInterfaceNumber = DYNAMIC */
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_COMM,
|
||||
.bInterfaceSubClass = USB_CDC_SUBCLASS_MDLM,
|
||||
.bInterfaceProtocol = 0,
|
||||
/* .iInterface = DYNAMIC */
|
||||
};
|
||||
|
||||
static struct usb_cdc_header_desc header_desc __initdata = {
|
||||
.bLength = sizeof header_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
|
||||
|
||||
.bcdCDC = __constant_cpu_to_le16(0x0110),
|
||||
};
|
||||
|
||||
static struct usb_cdc_mdlm_desc mdlm_desc __initdata = {
|
||||
.bLength = sizeof mdlm_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_MDLM_TYPE,
|
||||
|
||||
.bcdVersion = __constant_cpu_to_le16(0x0100),
|
||||
.bGUID = {
|
||||
0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6,
|
||||
0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f,
|
||||
},
|
||||
};
|
||||
|
||||
/* since "usb_cdc_mdlm_detail_desc" is a variable length structure, we
|
||||
* can't really use its struct. All we do here is say that we're using
|
||||
* the submode of "SAFE" which directly matches the CDC Subset.
|
||||
*/
|
||||
static u8 mdlm_detail_desc[] __initdata = {
|
||||
6,
|
||||
USB_DT_CS_INTERFACE,
|
||||
USB_CDC_MDLM_DETAIL_TYPE,
|
||||
|
||||
0, /* "SAFE" */
|
||||
0, /* network control capabilities (none) */
|
||||
0, /* network data capabilities ("raw" encapsulation) */
|
||||
};
|
||||
|
||||
static struct usb_cdc_ether_desc ether_desc __initdata = {
|
||||
.bLength = sizeof ether_desc,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubType = USB_CDC_ETHERNET_TYPE,
|
||||
|
||||
/* this descriptor actually adds value, surprise! */
|
||||
/* .iMACAddress = DYNAMIC */
|
||||
.bmEthernetStatistics = __constant_cpu_to_le32(0), /* no statistics */
|
||||
.wMaxSegmentSize = __constant_cpu_to_le16(ETH_FRAME_LEN),
|
||||
.wNumberMCFilters = __constant_cpu_to_le16(0),
|
||||
.bNumberPowerFilters = 0,
|
||||
};
|
||||
|
||||
/* full speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor fs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *fs_eth_function[] __initdata = {
|
||||
(struct usb_descriptor_header *) &subset_data_intf,
|
||||
(struct usb_descriptor_header *) &header_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_detail_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
(struct usb_descriptor_header *) &fs_in_desc,
|
||||
(struct usb_descriptor_header *) &fs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* high speed support: */
|
||||
|
||||
static struct usb_endpoint_descriptor hs_in_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor hs_out_desc __initdata = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = __constant_cpu_to_le16(512),
|
||||
};
|
||||
|
||||
static struct usb_descriptor_header *hs_eth_function[] __initdata = {
|
||||
(struct usb_descriptor_header *) &subset_data_intf,
|
||||
(struct usb_descriptor_header *) &header_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_desc,
|
||||
(struct usb_descriptor_header *) &mdlm_detail_desc,
|
||||
(struct usb_descriptor_header *) ðer_desc,
|
||||
(struct usb_descriptor_header *) &hs_in_desc,
|
||||
(struct usb_descriptor_header *) &hs_out_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* string descriptors: */
|
||||
|
||||
static struct usb_string geth_string_defs[] = {
|
||||
[0].s = "CDC Ethernet Subset/SAFE",
|
||||
[1].s = NULL /* DYNAMIC */,
|
||||
{ } /* end of list */
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings geth_string_table = {
|
||||
.language = 0x0409, /* en-us */
|
||||
.strings = geth_string_defs,
|
||||
};
|
||||
|
||||
static struct usb_gadget_strings *geth_strings[] = {
|
||||
&geth_string_table,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
{
|
||||
struct f_gether *geth = func_to_geth(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct net_device *net;
|
||||
|
||||
/* we know alt == 0, so this is an activation or a reset */
|
||||
|
||||
if (geth->port.in_ep->driver_data) {
|
||||
DBG(cdev, "reset cdc subset\n");
|
||||
gether_disconnect(&geth->port);
|
||||
}
|
||||
|
||||
DBG(cdev, "init + activate cdc subset\n");
|
||||
geth->port.in = ep_choose(cdev->gadget,
|
||||
geth->hs.in, geth->fs.in);
|
||||
geth->port.out = ep_choose(cdev->gadget,
|
||||
geth->hs.out, geth->fs.out);
|
||||
|
||||
net = gether_connect(&geth->port);
|
||||
return IS_ERR(net) ? PTR_ERR(net) : 0;
|
||||
}
|
||||
|
||||
static void geth_disable(struct usb_function *f)
|
||||
{
|
||||
struct f_gether *geth = func_to_geth(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
|
||||
DBG(cdev, "net deactivated\n");
|
||||
gether_disconnect(&geth->port);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* serial function driver setup/binding */
|
||||
|
||||
static int __init
|
||||
geth_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_gether *geth = func_to_geth(f);
|
||||
int status;
|
||||
struct usb_ep *ep;
|
||||
|
||||
/* allocate instance-specific interface IDs */
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
subset_data_intf.bInterfaceNumber = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
/* allocate instance-specific endpoints */
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
geth->port.in_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
|
||||
if (!ep)
|
||||
goto fail;
|
||||
geth->port.out_ep = ep;
|
||||
ep->driver_data = cdev; /* claim */
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->descriptors = usb_copy_descriptors(fs_eth_function);
|
||||
|
||||
geth->fs.in = usb_find_endpoint(fs_eth_function,
|
||||
f->descriptors, &fs_in_desc);
|
||||
geth->fs.out = usb_find_endpoint(fs_eth_function,
|
||||
f->descriptors, &fs_out_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)) {
|
||||
hs_in_desc.bEndpointAddress =
|
||||
fs_in_desc.bEndpointAddress;
|
||||
hs_out_desc.bEndpointAddress =
|
||||
fs_out_desc.bEndpointAddress;
|
||||
|
||||
/* copy descriptors, and track endpoint copies */
|
||||
f->hs_descriptors = usb_copy_descriptors(hs_eth_function);
|
||||
|
||||
geth->hs.in = usb_find_endpoint(hs_eth_function,
|
||||
f->hs_descriptors, &hs_in_desc);
|
||||
geth->hs.out = usb_find_endpoint(hs_eth_function,
|
||||
f->hs_descriptors, &hs_out_desc);
|
||||
}
|
||||
|
||||
/* NOTE: all that is done without knowing or caring about
|
||||
* the network link ... which is unavailable to this code
|
||||
* until we're activated via set_alt().
|
||||
*/
|
||||
|
||||
DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n",
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
geth->port.in_ep->name, geth->port.out_ep->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/* we might as well release our claims on endpoints */
|
||||
if (geth->port.out)
|
||||
geth->port.out_ep->driver_data = NULL;
|
||||
if (geth->port.in)
|
||||
geth->port.in_ep->driver_data = NULL;
|
||||
|
||||
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
geth_unbind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
if (gadget_is_dualspeed(c->cdev->gadget))
|
||||
usb_free_descriptors(f->hs_descriptors);
|
||||
usb_free_descriptors(f->descriptors);
|
||||
geth_string_defs[1].s = NULL;
|
||||
kfree(func_to_geth(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* geth_bind_config - add CDC Subset network link to a configuration
|
||||
* @c: the configuration to support the network link
|
||||
* @ethaddr: a buffer in which the ethernet address of the host side
|
||||
* side of the link was recorded
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*
|
||||
* Caller must have called @gether_setup(). Caller is also responsible
|
||||
* for calling @gether_cleanup() before module unload.
|
||||
*/
|
||||
int __init geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
|
||||
{
|
||||
struct f_gether *geth;
|
||||
int status;
|
||||
|
||||
if (!ethaddr)
|
||||
return -EINVAL;
|
||||
|
||||
/* maybe allocate device-global string IDs */
|
||||
if (geth_string_defs[0].id == 0) {
|
||||
|
||||
/* interface label */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
geth_string_defs[0].id = status;
|
||||
subset_data_intf.iInterface = status;
|
||||
|
||||
/* MAC address */
|
||||
status = usb_string_id(c->cdev);
|
||||
if (status < 0)
|
||||
return status;
|
||||
geth_string_defs[1].id = status;
|
||||
ether_desc.iMACAddress = status;
|
||||
}
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
geth = kzalloc(sizeof *geth, GFP_KERNEL);
|
||||
if (!geth)
|
||||
return -ENOMEM;
|
||||
|
||||
/* export host's Ethernet address in CDC format */
|
||||
snprintf(geth->ethaddr, sizeof geth->ethaddr,
|
||||
"%02X%02X%02X%02X%02X%02X",
|
||||
ethaddr[0], ethaddr[1], ethaddr[2],
|
||||
ethaddr[3], ethaddr[4], ethaddr[5]);
|
||||
geth_string_defs[1].s = geth->ethaddr;
|
||||
|
||||
geth->port.cdc_filter = DEFAULT_FILTER;
|
||||
|
||||
geth->port.func.name = "cdc_subset";
|
||||
geth->port.func.strings = geth_strings;
|
||||
geth->port.func.bind = geth_bind;
|
||||
geth->port.func.unbind = geth_unbind;
|
||||
geth->port.func.set_alt = geth_set_alt;
|
||||
geth->port.func.disable = geth_disable;
|
||||
|
||||
status = usb_add_function(c, &geth->port.func);
|
||||
if (status) {
|
||||
geth_string_defs[1].s = NULL;
|
||||
kfree(geth);
|
||||
}
|
||||
return status;
|
||||
}
|
@ -3867,8 +3867,8 @@ static int __init fsg_bind(struct usb_gadget *gadget)
|
||||
curlun->dev.parent = &gadget->dev;
|
||||
curlun->dev.driver = &fsg_driver.driver;
|
||||
dev_set_drvdata(&curlun->dev, fsg);
|
||||
snprintf(curlun->dev.bus_id, BUS_ID_SIZE,
|
||||
"%s-lun%d", gadget->dev.bus_id, i);
|
||||
dev_set_name(&curlun->dev,"%s-lun%d",
|
||||
dev_name(&gadget->dev), i);
|
||||
|
||||
if ((rc = device_register(&curlun->dev)) != 0) {
|
||||
INFO(fsg, "failed to register LUN%d: %d\n", i, rc);
|
||||
|
@ -2331,7 +2331,7 @@ static int __init fsl_udc_probe(struct platform_device *pdev)
|
||||
udc_controller->gadget.name = driver_name;
|
||||
|
||||
/* Setup gadget.dev and register with kernel */
|
||||
strcpy(udc_controller->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&udc_controller->gadget.dev, "gadget");
|
||||
udc_controller->gadget.dev.release = fsl_udc_release;
|
||||
udc_controller->gadget.dev.parent = &pdev->dev;
|
||||
ret = device_register(&udc_controller->gadget.dev);
|
||||
|
25
drivers/usb/gadget/g_zero.h
Normal file
25
drivers/usb/gadget/g_zero.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* This header declares the utility functions used by "Gadget Zero", plus
|
||||
* interfaces to its two single-configuration function drivers.
|
||||
*/
|
||||
|
||||
#ifndef __G_ZERO_H
|
||||
#define __G_ZERO_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
/* global state */
|
||||
extern unsigned buflen;
|
||||
extern const struct usb_descriptor_header *otg_desc[];
|
||||
|
||||
/* common utilities */
|
||||
struct usb_request *alloc_ep_req(struct usb_ep *ep);
|
||||
void free_ep_req(struct usb_ep *ep, struct usb_request *req);
|
||||
void disable_endpoints(struct usb_composite_dev *cdev,
|
||||
struct usb_ep *in, struct usb_ep *out);
|
||||
|
||||
/* configuration-specific linkup */
|
||||
int sourcesink_add(struct usb_composite_dev *cdev);
|
||||
int loopback_add(struct usb_composite_dev *cdev);
|
||||
|
||||
#endif /* __G_ZERO_H */
|
@ -214,3 +214,26 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
|
||||
return 0x21;
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* gadget_supports_altsettings - return true if altsettings work
|
||||
* @gadget: the gadget in question
|
||||
*/
|
||||
static inline bool gadget_supports_altsettings(struct usb_gadget *gadget)
|
||||
{
|
||||
/* PXA 21x/25x/26x has no altsettings at all */
|
||||
if (gadget_is_pxa(gadget))
|
||||
return false;
|
||||
|
||||
/* PXA 27x and 3xx have *broken* altsetting support */
|
||||
if (gadget_is_pxa27x(gadget))
|
||||
return false;
|
||||
|
||||
/* SH3 hardware just doesn't do altsettings */
|
||||
if (gadget_is_sh(gadget))
|
||||
return false;
|
||||
|
||||
/* Everything else is *presumably* fine ... */
|
||||
return true;
|
||||
}
|
||||
|
@ -1790,7 +1790,7 @@ static int goku_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
dev->gadget.ops = &goku_ops;
|
||||
|
||||
/* the "gadget" abstracts/virtualizes the controller */
|
||||
strcpy(dev->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&dev->gadget.dev, "gadget");
|
||||
dev->gadget.dev.parent = &pdev->dev;
|
||||
dev->gadget.dev.dma_mask = pdev->dev.dma_mask;
|
||||
dev->gadget.dev.release = gadget_release;
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/smp_lock.h>
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/moduleparam.h>
|
||||
@ -483,8 +484,7 @@ ep_release (struct inode *inode, struct file *fd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ep_ioctl (struct inode *inode, struct file *fd,
|
||||
unsigned code, unsigned long value)
|
||||
static long ep_ioctl(struct file *fd, unsigned code, unsigned long value)
|
||||
{
|
||||
struct ep_data *data = fd->private_data;
|
||||
int status;
|
||||
@ -740,7 +740,7 @@ static const struct file_operations ep_io_operations = {
|
||||
|
||||
.read = ep_read,
|
||||
.write = ep_write,
|
||||
.ioctl = ep_ioctl,
|
||||
.unlocked_ioctl = ep_ioctl,
|
||||
.release = ep_release,
|
||||
|
||||
.aio_read = ep_aio_read,
|
||||
@ -1294,15 +1294,18 @@ ep0_poll (struct file *fd, poll_table *wait)
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int dev_ioctl (struct inode *inode, struct file *fd,
|
||||
unsigned code, unsigned long value)
|
||||
static long dev_ioctl (struct file *fd, unsigned code, unsigned long value)
|
||||
{
|
||||
struct dev_data *dev = fd->private_data;
|
||||
struct usb_gadget *gadget = dev->gadget;
|
||||
long ret = -ENOTTY;
|
||||
|
||||
if (gadget->ops->ioctl)
|
||||
return gadget->ops->ioctl (gadget, code, value);
|
||||
return -ENOTTY;
|
||||
if (gadget->ops->ioctl) {
|
||||
lock_kernel();
|
||||
ret = gadget->ops->ioctl (gadget, code, value);
|
||||
unlock_kernel();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* used after device configuration */
|
||||
@ -1314,7 +1317,7 @@ static const struct file_operations ep0_io_operations = {
|
||||
.write = ep0_write,
|
||||
.fasync = ep0_fasync,
|
||||
.poll = ep0_poll,
|
||||
.ioctl = dev_ioctl,
|
||||
.unlocked_ioctl = dev_ioctl,
|
||||
.release = dev_release,
|
||||
};
|
||||
|
||||
@ -1964,7 +1967,7 @@ static const struct file_operations dev_init_operations = {
|
||||
.open = dev_open,
|
||||
.write = dev_config,
|
||||
.fasync = ep0_fasync,
|
||||
.ioctl = dev_ioctl,
|
||||
.unlocked_ioctl = dev_ioctl,
|
||||
.release = dev_release,
|
||||
};
|
||||
|
||||
|
@ -1970,7 +1970,7 @@ static const struct usb_gadget_ops lh7a40x_udc_ops = {
|
||||
|
||||
static void nop_release(struct device *dev)
|
||||
{
|
||||
DEBUG("%s %s\n", __func__, dev->bus_id);
|
||||
DEBUG("%s %s\n", __func__, dev_name(dev));
|
||||
}
|
||||
|
||||
static struct lh7a40x_udc memory = {
|
||||
|
@ -1593,7 +1593,7 @@ static int __init m66592_probe(struct platform_device *pdev)
|
||||
|
||||
m66592->gadget.ops = &m66592_gadget_ops;
|
||||
device_initialize(&m66592->gadget.dev);
|
||||
strcpy(m66592->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&m66592->gadget, "gadget");
|
||||
m66592->gadget.is_dualspeed = 1;
|
||||
m66592->gadget.dev.parent = &pdev->dev;
|
||||
m66592->gadget.dev.dma_mask = pdev->dev.dma_mask;
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* ndis.h
|
||||
*
|
||||
* ndis.h
|
||||
*
|
||||
* ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de>
|
||||
*
|
||||
* Thanks to the cygwin development team,
|
||||
*
|
||||
* Thanks to the cygwin development team,
|
||||
* espacially to Casper S. Hornstrup <chorns@users.sourceforge.net>
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS NOT COPYRIGHTED
|
||||
*
|
||||
* This source code is offered for use in the public domain. You may
|
||||
|
@ -2768,7 +2768,7 @@ static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
dev->gadget.is_dualspeed = 1;
|
||||
|
||||
/* the "gadget" abstracts/virtualizes the controller */
|
||||
strcpy (dev->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&dev->gadget.dev, "gadget");
|
||||
dev->gadget.dev.parent = &pdev->dev;
|
||||
dev->gadget.dev.dma_mask = pdev->dev.dma_mask;
|
||||
dev->gadget.dev.release = gadget_release;
|
||||
|
@ -2686,7 +2686,7 @@ omap_udc_setup(struct platform_device *odev, struct otg_transceiver *xceiv)
|
||||
udc->gadget.name = driver_name;
|
||||
|
||||
device_initialize(&udc->gadget.dev);
|
||||
strcpy (udc->gadget.dev.bus_id, "gadget");
|
||||
dev_set_name(&udc->gadget.dev, "gadget");
|
||||
udc->gadget.dev.release = omap_udc_release;
|
||||
udc->gadget.dev.parent = &odev->dev;
|
||||
if (use_dma)
|
||||
|
@ -828,9 +828,8 @@ printer_poll(struct file *fd, poll_table *wait)
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
printer_ioctl(struct inode *inode, struct file *fd, unsigned int code,
|
||||
unsigned long arg)
|
||||
static long
|
||||
printer_ioctl(struct file *fd, unsigned int code, unsigned long arg)
|
||||
{
|
||||
struct printer_dev *dev = fd->private_data;
|
||||
unsigned long flags;
|
||||
@ -869,7 +868,7 @@ static struct file_operations printer_io_operations = {
|
||||
.write = printer_write,
|
||||
.fsync = printer_fsync,
|
||||
.poll = printer_poll,
|
||||
.ioctl = printer_ioctl,
|
||||
.unlocked_ioctl = printer_ioctl,
|
||||
.release = printer_close
|
||||
};
|
||||
|
||||
|
@ -1818,7 +1818,7 @@ pxa25x_udc_irq(int irq, void *_dev)
|
||||
|
||||
static void nop_release (struct device *dev)
|
||||
{
|
||||
DMSG("%s %s\n", __func__, dev->bus_id);
|
||||
DMSG("%s %s\n", __func__, dev_name(dev));
|
||||
}
|
||||
|
||||
/* this uses load-time allocation and initialization (instead of
|
||||
|
@ -1575,7 +1575,6 @@ static void udc_enable(struct pxa_udc *udc)
|
||||
{
|
||||
udc_writel(udc, UDCICR0, 0);
|
||||
udc_writel(udc, UDCICR1, 0);
|
||||
udc_writel(udc, UP2OCR, UP2OCR_HXOE);
|
||||
udc_clear_mask_UDCCR(udc, UDCCR_UDE);
|
||||
|
||||
clk_enable(udc->clk);
|
||||
|
@ -1,8 +1,6 @@
|
||||
/*
|
||||
* RNDIS MSG parser
|
||||
*
|
||||
* Version: $Id: rndis.c,v 1.19 2004/03/25 21:33:46 robert Exp $
|
||||
*
|
||||
* Authors: Benedikt Spranger, Pengutronix
|
||||
* Robert Schwebel, Pengutronix
|
||||
*
|
||||
@ -30,6 +28,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
@ -38,9 +37,7 @@
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
|
||||
#undef RNDIS_PM
|
||||
#undef RNDIS_WAKEUP
|
||||
#undef VERBOSE
|
||||
#undef VERBOSE_DEBUG
|
||||
|
||||
#include "rndis.h"
|
||||
|
||||
@ -96,9 +93,6 @@ static const u32 oid_supported_list [] =
|
||||
OID_GEN_MAXIMUM_TOTAL_SIZE,
|
||||
OID_GEN_MEDIA_CONNECT_STATUS,
|
||||
OID_GEN_PHYSICAL_MEDIUM,
|
||||
#if 0
|
||||
OID_GEN_RNDIS_CONFIG_PARAMETER,
|
||||
#endif
|
||||
|
||||
/* the statistical stuff */
|
||||
OID_GEN_XMIT_OK,
|
||||
@ -146,7 +140,14 @@ static const u32 oid_supported_list [] =
|
||||
#endif /* RNDIS_OPTIONAL_STATS */
|
||||
|
||||
#ifdef RNDIS_PM
|
||||
/* PM and wakeup are mandatory for USB: */
|
||||
/* PM and wakeup are "mandatory" for USB, but the RNDIS specs
|
||||
* don't say what they mean ... and the NDIS specs are often
|
||||
* confusing and/or ambiguous in this context. (That is, more
|
||||
* so than their specs for the other OIDs.)
|
||||
*
|
||||
* FIXME someone who knows what these should do, please
|
||||
* implement them!
|
||||
*/
|
||||
|
||||
/* power management */
|
||||
OID_PNP_CAPABILITIES,
|
||||
@ -173,6 +174,8 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
__le32 *outbuf;
|
||||
int i, count;
|
||||
rndis_query_cmplt_type *resp;
|
||||
struct net_device *net;
|
||||
struct net_device_stats *stats;
|
||||
|
||||
if (!r) return -ENOMEM;
|
||||
resp = (rndis_query_cmplt_type *) r->buf;
|
||||
@ -194,6 +197,12 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
outbuf = (__le32 *) &resp[1];
|
||||
resp->InformationBufferOffset = __constant_cpu_to_le32 (16);
|
||||
|
||||
net = rndis_per_dev_params[configNr].dev;
|
||||
if (net->get_stats)
|
||||
stats = net->get_stats(net);
|
||||
else
|
||||
stats = NULL;
|
||||
|
||||
switch (OID) {
|
||||
|
||||
/* general oids (table 4-1) */
|
||||
@ -350,11 +359,9 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
case OID_GEN_XMIT_OK:
|
||||
if (rndis_debug > 1)
|
||||
DBG("%s: OID_GEN_XMIT_OK\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (
|
||||
rndis_per_dev_params [configNr].stats->tx_packets -
|
||||
rndis_per_dev_params [configNr].stats->tx_errors -
|
||||
rndis_per_dev_params [configNr].stats->tx_dropped);
|
||||
if (stats) {
|
||||
*outbuf = cpu_to_le32(stats->tx_packets
|
||||
- stats->tx_errors - stats->tx_dropped);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
@ -363,11 +370,9 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
case OID_GEN_RCV_OK:
|
||||
if (rndis_debug > 1)
|
||||
DBG("%s: OID_GEN_RCV_OK\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (
|
||||
rndis_per_dev_params [configNr].stats->rx_packets -
|
||||
rndis_per_dev_params [configNr].stats->rx_errors -
|
||||
rndis_per_dev_params [configNr].stats->rx_dropped);
|
||||
if (stats) {
|
||||
*outbuf = cpu_to_le32(stats->rx_packets
|
||||
- stats->rx_errors - stats->rx_dropped);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
@ -376,9 +381,8 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
case OID_GEN_XMIT_ERROR:
|
||||
if (rndis_debug > 1)
|
||||
DBG("%s: OID_GEN_XMIT_ERROR\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->tx_errors);
|
||||
if (stats) {
|
||||
*outbuf = cpu_to_le32(stats->tx_errors);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
@ -387,9 +391,8 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
case OID_GEN_RCV_ERROR:
|
||||
if (rndis_debug > 1)
|
||||
DBG("%s: OID_GEN_RCV_ERROR\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->rx_errors);
|
||||
if (stats) {
|
||||
*outbuf = cpu_to_le32(stats->rx_errors);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
@ -397,150 +400,12 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
/* mandatory */
|
||||
case OID_GEN_RCV_NO_BUFFER:
|
||||
DBG("%s: OID_GEN_RCV_NO_BUFFER\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->rx_dropped);
|
||||
if (stats) {
|
||||
*outbuf = cpu_to_le32(stats->rx_dropped);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef RNDIS_OPTIONAL_STATS
|
||||
case OID_GEN_DIRECTED_BYTES_XMIT:
|
||||
DBG("%s: OID_GEN_DIRECTED_BYTES_XMIT\n", __func__);
|
||||
/*
|
||||
* Aunt Tilly's size of shoes
|
||||
* minus antarctica count of penguins
|
||||
* divided by weight of Alpha Centauri
|
||||
*/
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (
|
||||
(rndis_per_dev_params [configNr]
|
||||
.stats->tx_packets -
|
||||
rndis_per_dev_params [configNr]
|
||||
.stats->tx_errors -
|
||||
rndis_per_dev_params [configNr]
|
||||
.stats->tx_dropped)
|
||||
* 123);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_DIRECTED_FRAMES_XMIT:
|
||||
DBG("%s: OID_GEN_DIRECTED_FRAMES_XMIT\n", __func__);
|
||||
/* dito */
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (
|
||||
(rndis_per_dev_params [configNr]
|
||||
.stats->tx_packets -
|
||||
rndis_per_dev_params [configNr]
|
||||
.stats->tx_errors -
|
||||
rndis_per_dev_params [configNr]
|
||||
.stats->tx_dropped)
|
||||
/ 123);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_MULTICAST_BYTES_XMIT:
|
||||
DBG("%s: OID_GEN_MULTICAST_BYTES_XMIT\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->multicast*1234);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_MULTICAST_FRAMES_XMIT:
|
||||
DBG("%s: OID_GEN_MULTICAST_FRAMES_XMIT\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->multicast);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_BROADCAST_BYTES_XMIT:
|
||||
DBG("%s: OID_GEN_BROADCAST_BYTES_XMIT\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->tx_packets/42*255);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_BROADCAST_FRAMES_XMIT:
|
||||
DBG("%s: OID_GEN_BROADCAST_FRAMES_XMIT\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->tx_packets/42);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_DIRECTED_BYTES_RCV:
|
||||
DBG("%s: OID_GEN_DIRECTED_BYTES_RCV\n", __func__);
|
||||
*outbuf = __constant_cpu_to_le32 (0);
|
||||
retval = 0;
|
||||
break;
|
||||
|
||||
case OID_GEN_DIRECTED_FRAMES_RCV:
|
||||
DBG("%s: OID_GEN_DIRECTED_FRAMES_RCV\n", __func__);
|
||||
*outbuf = __constant_cpu_to_le32 (0);
|
||||
retval = 0;
|
||||
break;
|
||||
|
||||
case OID_GEN_MULTICAST_BYTES_RCV:
|
||||
DBG("%s: OID_GEN_MULTICAST_BYTES_RCV\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->multicast * 1111);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_MULTICAST_FRAMES_RCV:
|
||||
DBG("%s: OID_GEN_MULTICAST_FRAMES_RCV\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->multicast);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_BROADCAST_BYTES_RCV:
|
||||
DBG("%s: OID_GEN_BROADCAST_BYTES_RCV\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->rx_packets/42*255);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_BROADCAST_FRAMES_RCV:
|
||||
DBG("%s: OID_GEN_BROADCAST_FRAMES_RCV\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->rx_packets/42);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_RCV_CRC_ERROR:
|
||||
DBG("%s: OID_GEN_RCV_CRC_ERROR\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->rx_crc_errors);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OID_GEN_TRANSMIT_QUEUE_LENGTH:
|
||||
DBG("%s: OID_GEN_TRANSMIT_QUEUE_LENGTH\n", __func__);
|
||||
*outbuf = __constant_cpu_to_le32 (0);
|
||||
retval = 0;
|
||||
break;
|
||||
#endif /* RNDIS_OPTIONAL_STATS */
|
||||
|
||||
/* ieee802.3 OIDs (table 4-3) */
|
||||
|
||||
/* mandatory */
|
||||
@ -592,9 +457,8 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
/* mandatory */
|
||||
case OID_802_3_RCV_ERROR_ALIGNMENT:
|
||||
DBG("%s: OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__);
|
||||
if (rndis_per_dev_params [configNr].stats) {
|
||||
*outbuf = cpu_to_le32 (rndis_per_dev_params [configNr]
|
||||
.stats->rx_frame_errors);
|
||||
if (stats) {
|
||||
*outbuf = cpu_to_le32(stats->rx_frame_errors);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
@ -613,64 +477,6 @@ gen_ndis_query_resp (int configNr, u32 OID, u8 *buf, unsigned buf_len,
|
||||
retval = 0;
|
||||
break;
|
||||
|
||||
#ifdef RNDIS_OPTIONAL_STATS
|
||||
case OID_802_3_XMIT_DEFERRED:
|
||||
DBG("%s: OID_802_3_XMIT_DEFERRED\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
|
||||
case OID_802_3_XMIT_MAX_COLLISIONS:
|
||||
DBG("%s: OID_802_3_XMIT_MAX_COLLISIONS\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
|
||||
case OID_802_3_RCV_OVERRUN:
|
||||
DBG("%s: OID_802_3_RCV_OVERRUN\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
|
||||
case OID_802_3_XMIT_UNDERRUN:
|
||||
DBG("%s: OID_802_3_XMIT_UNDERRUN\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
|
||||
case OID_802_3_XMIT_HEARTBEAT_FAILURE:
|
||||
DBG("%s: OID_802_3_XMIT_HEARTBEAT_FAILURE\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
|
||||
case OID_802_3_XMIT_TIMES_CRS_LOST:
|
||||
DBG("%s: OID_802_3_XMIT_TIMES_CRS_LOST\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
|
||||
case OID_802_3_XMIT_LATE_COLLISIONS:
|
||||
DBG("%s: OID_802_3_XMIT_LATE_COLLISIONS\n", __func__);
|
||||
/* TODO */
|
||||
break;
|
||||
#endif /* RNDIS_OPTIONAL_STATS */
|
||||
|
||||
#ifdef RNDIS_PM
|
||||
/* power management OIDs (table 4-5) */
|
||||
case OID_PNP_CAPABILITIES:
|
||||
DBG("%s: OID_PNP_CAPABILITIES\n", __func__);
|
||||
|
||||
/* for now, no wakeup capabilities */
|
||||
length = sizeof (struct NDIS_PNP_CAPABILITIES);
|
||||
memset(outbuf, 0, length);
|
||||
retval = 0;
|
||||
break;
|
||||
case OID_PNP_QUERY_POWER:
|
||||
DBG("%s: OID_PNP_QUERY_POWER D%d\n", __func__,
|
||||
get_unaligned_le32(buf) - 1);
|
||||
/* only suspend is a real power state, and
|
||||
* it can't be entered by OID_PNP_SET_POWER...
|
||||
*/
|
||||
length = 0;
|
||||
retval = 0;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
pr_warning("%s: query unknown OID 0x%08X\n",
|
||||
__func__, OID);
|
||||
@ -726,9 +532,6 @@ static int gen_ndis_set_resp (u8 configNr, u32 OID, u8 *buf, u32 buf_len,
|
||||
* what makes the packet flow start and stop, like
|
||||
* activating the CDC Ethernet altsetting.
|
||||
*/
|
||||
#ifdef RNDIS_PM
|
||||
update_linkstate:
|
||||
#endif
|
||||
retval = 0;
|
||||
if (*params->filter) {
|
||||
params->state = RNDIS_DATA_INITIALIZED;
|
||||
@ -747,49 +550,6 @@ static int gen_ndis_set_resp (u8 configNr, u32 OID, u8 *buf, u32 buf_len,
|
||||
DBG("%s: OID_802_3_MULTICAST_LIST\n", __func__);
|
||||
retval = 0;
|
||||
break;
|
||||
#if 0
|
||||
case OID_GEN_RNDIS_CONFIG_PARAMETER:
|
||||
{
|
||||
struct rndis_config_parameter *param;
|
||||
param = (struct rndis_config_parameter *) buf;
|
||||
DBG("%s: OID_GEN_RNDIS_CONFIG_PARAMETER '%*s'\n",
|
||||
__func__,
|
||||
min(cpu_to_le32(param->ParameterNameLength),80),
|
||||
buf + param->ParameterNameOffset);
|
||||
retval = 0;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef RNDIS_PM
|
||||
case OID_PNP_SET_POWER:
|
||||
/* The only real power state is USB suspend, and RNDIS requests
|
||||
* can't enter it; this one isn't really about power. After
|
||||
* resuming, Windows forces a reset, and then SET_POWER D0.
|
||||
* FIXME ... then things go batty; Windows wedges itself.
|
||||
*/
|
||||
i = get_unaligned_le32(buf);
|
||||
DBG("%s: OID_PNP_SET_POWER D%d\n", __func__, i - 1);
|
||||
switch (i) {
|
||||
case NdisDeviceStateD0:
|
||||
*params->filter = params->saved_filter;
|
||||
goto update_linkstate;
|
||||
case NdisDeviceStateD3:
|
||||
case NdisDeviceStateD2:
|
||||
case NdisDeviceStateD1:
|
||||
params->saved_filter = *params->filter;
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef RNDIS_WAKEUP
|
||||
// no wakeup support advertised, so wakeup OIDs always fail:
|
||||
// - OID_PNP_ENABLE_WAKE_UP
|
||||
// - OID_PNP_{ADD,REMOVE}_WAKE_UP_PATTERN
|
||||
#endif
|
||||
|
||||
#endif /* RNDIS_PM */
|
||||
|
||||
default:
|
||||
pr_warning("%s: set unknown OID 0x%08X, size %d\n",
|
||||
@ -807,8 +567,10 @@ static int rndis_init_response (int configNr, rndis_init_msg_type *buf)
|
||||
{
|
||||
rndis_init_cmplt_type *resp;
|
||||
rndis_resp_t *r;
|
||||
struct rndis_params *params = rndis_per_dev_params + configNr;
|
||||
|
||||
if (!rndis_per_dev_params [configNr].dev) return -ENOTSUPP;
|
||||
if (!params->dev)
|
||||
return -ENOTSUPP;
|
||||
|
||||
r = rndis_add_response (configNr, sizeof (rndis_init_cmplt_type));
|
||||
if (!r)
|
||||
@ -826,7 +588,7 @@ static int rndis_init_response (int configNr, rndis_init_msg_type *buf)
|
||||
resp->Medium = __constant_cpu_to_le32 (RNDIS_MEDIUM_802_3);
|
||||
resp->MaxPacketsPerTransfer = __constant_cpu_to_le32 (1);
|
||||
resp->MaxTransferSize = cpu_to_le32 (
|
||||
rndis_per_dev_params [configNr].dev->mtu
|
||||
params->dev->mtu
|
||||
+ sizeof (struct ethhdr)
|
||||
+ sizeof (struct rndis_packet_msg_type)
|
||||
+ 22);
|
||||
@ -834,10 +596,7 @@ static int rndis_init_response (int configNr, rndis_init_msg_type *buf)
|
||||
resp->AFListOffset = __constant_cpu_to_le32 (0);
|
||||
resp->AFListSize = __constant_cpu_to_le32 (0);
|
||||
|
||||
if (rndis_per_dev_params [configNr].ack)
|
||||
rndis_per_dev_params [configNr].ack (
|
||||
rndis_per_dev_params [configNr].dev);
|
||||
|
||||
params->resp_avail(params->v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -845,9 +604,11 @@ static int rndis_query_response (int configNr, rndis_query_msg_type *buf)
|
||||
{
|
||||
rndis_query_cmplt_type *resp;
|
||||
rndis_resp_t *r;
|
||||
struct rndis_params *params = rndis_per_dev_params + configNr;
|
||||
|
||||
// DBG("%s: OID = %08X\n", __func__, cpu_to_le32(buf->OID));
|
||||
if (!rndis_per_dev_params [configNr].dev) return -ENOTSUPP;
|
||||
if (!params->dev)
|
||||
return -ENOTSUPP;
|
||||
|
||||
/*
|
||||
* we need more memory:
|
||||
@ -878,9 +639,7 @@ static int rndis_query_response (int configNr, rndis_query_msg_type *buf)
|
||||
} else
|
||||
resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS);
|
||||
|
||||
if (rndis_per_dev_params [configNr].ack)
|
||||
rndis_per_dev_params [configNr].ack (
|
||||
rndis_per_dev_params [configNr].dev);
|
||||
params->resp_avail(params->v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -889,6 +648,7 @@ static int rndis_set_response (int configNr, rndis_set_msg_type *buf)
|
||||
u32 BufLength, BufOffset;
|
||||
rndis_set_cmplt_type *resp;
|
||||
rndis_resp_t *r;
|
||||
struct rndis_params *params = rndis_per_dev_params + configNr;
|
||||
|
||||
r = rndis_add_response (configNr, sizeof (rndis_set_cmplt_type));
|
||||
if (!r)
|
||||
@ -898,7 +658,7 @@ static int rndis_set_response (int configNr, rndis_set_msg_type *buf)
|
||||
BufLength = le32_to_cpu (buf->InformationBufferLength);
|
||||
BufOffset = le32_to_cpu (buf->InformationBufferOffset);
|
||||
|
||||
#ifdef VERBOSE
|
||||
#ifdef VERBOSE_DEBUG
|
||||
DBG("%s: Length: %d\n", __func__, BufLength);
|
||||
DBG("%s: Offset: %d\n", __func__, BufOffset);
|
||||
DBG("%s: InfoBuffer: ", __func__);
|
||||
@ -919,10 +679,7 @@ static int rndis_set_response (int configNr, rndis_set_msg_type *buf)
|
||||
else
|
||||
resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS);
|
||||
|
||||
if (rndis_per_dev_params [configNr].ack)
|
||||
rndis_per_dev_params [configNr].ack (
|
||||
rndis_per_dev_params [configNr].dev);
|
||||
|
||||
params->resp_avail(params->v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -930,6 +687,7 @@ static int rndis_reset_response (int configNr, rndis_reset_msg_type *buf)
|
||||
{
|
||||
rndis_reset_cmplt_type *resp;
|
||||
rndis_resp_t *r;
|
||||
struct rndis_params *params = rndis_per_dev_params + configNr;
|
||||
|
||||
r = rndis_add_response (configNr, sizeof (rndis_reset_cmplt_type));
|
||||
if (!r)
|
||||
@ -942,10 +700,7 @@ static int rndis_reset_response (int configNr, rndis_reset_msg_type *buf)
|
||||
/* resent information */
|
||||
resp->AddressingReset = __constant_cpu_to_le32 (1);
|
||||
|
||||
if (rndis_per_dev_params [configNr].ack)
|
||||
rndis_per_dev_params [configNr].ack (
|
||||
rndis_per_dev_params [configNr].dev);
|
||||
|
||||
params->resp_avail(params->v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -954,6 +709,7 @@ static int rndis_keepalive_response (int configNr,
|
||||
{
|
||||
rndis_keepalive_cmplt_type *resp;
|
||||
rndis_resp_t *r;
|
||||
struct rndis_params *params = rndis_per_dev_params + configNr;
|
||||
|
||||
/* host "should" check only in RNDIS_DATA_INITIALIZED state */
|
||||
|
||||
@ -968,10 +724,7 @@ static int rndis_keepalive_response (int configNr,
|
||||
resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
|
||||
resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS);
|
||||
|
||||
if (rndis_per_dev_params [configNr].ack)
|
||||
rndis_per_dev_params [configNr].ack (
|
||||
rndis_per_dev_params [configNr].dev);
|
||||
|
||||
params->resp_avail(params->v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -983,8 +736,9 @@ static int rndis_indicate_status_msg (int configNr, u32 status)
|
||||
{
|
||||
rndis_indicate_status_msg_type *resp;
|
||||
rndis_resp_t *r;
|
||||
struct rndis_params *params = rndis_per_dev_params + configNr;
|
||||
|
||||
if (rndis_per_dev_params [configNr].state == RNDIS_UNINITIALIZED)
|
||||
if (params->state == RNDIS_UNINITIALIZED)
|
||||
return -ENOTSUPP;
|
||||
|
||||
r = rndis_add_response (configNr,
|
||||
@ -1000,9 +754,7 @@ static int rndis_indicate_status_msg (int configNr, u32 status)
|
||||
resp->StatusBufferLength = __constant_cpu_to_le32 (0);
|
||||
resp->StatusBufferOffset = __constant_cpu_to_le32 (0);
|
||||
|
||||
if (rndis_per_dev_params [configNr].ack)
|
||||
rndis_per_dev_params [configNr].ack (
|
||||
rndis_per_dev_params [configNr].dev);
|
||||
params->resp_avail(params->v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1029,7 +781,6 @@ void rndis_uninit (int configNr)
|
||||
|
||||
if (configNr >= RNDIS_MAX_CONFIGS)
|
||||
return;
|
||||
rndis_per_dev_params [configNr].used = 0;
|
||||
rndis_per_dev_params [configNr].state = RNDIS_UNINITIALIZED;
|
||||
|
||||
/* drain the response queue */
|
||||
@ -1142,21 +893,25 @@ int rndis_msg_parser (u8 configNr, u8 *buf)
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
int rndis_register (int (* rndis_control_ack) (struct net_device *))
|
||||
int rndis_register(void (*resp_avail)(void *v), void *v)
|
||||
{
|
||||
u8 i;
|
||||
|
||||
if (!resp_avail)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
|
||||
if (!rndis_per_dev_params [i].used) {
|
||||
rndis_per_dev_params [i].used = 1;
|
||||
rndis_per_dev_params [i].ack = rndis_control_ack;
|
||||
rndis_per_dev_params [i].resp_avail = resp_avail;
|
||||
rndis_per_dev_params [i].v = v;
|
||||
DBG("%s: configNr = %d\n", __func__, i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
DBG("failed\n");
|
||||
|
||||
return -1;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
void rndis_deregister (int configNr)
|
||||
@ -1169,16 +924,14 @@ void rndis_deregister (int configNr)
|
||||
return;
|
||||
}
|
||||
|
||||
int rndis_set_param_dev (u8 configNr, struct net_device *dev,
|
||||
struct net_device_stats *stats,
|
||||
u16 *cdc_filter)
|
||||
int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter)
|
||||
{
|
||||
DBG("%s:\n", __func__ );
|
||||
if (!dev || !stats) return -1;
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
if (configNr >= RNDIS_MAX_CONFIGS) return -1;
|
||||
|
||||
rndis_per_dev_params [configNr].dev = dev;
|
||||
rndis_per_dev_params [configNr].stats = stats;
|
||||
rndis_per_dev_params [configNr].filter = cdc_filter;
|
||||
|
||||
return 0;
|
||||
@ -1296,14 +1049,11 @@ int rndis_rm_hdr(struct sk_buff *skb)
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
||||
|
||||
static int rndis_proc_read (char *page, char **start, off_t off, int count, int *eof,
|
||||
void *data)
|
||||
static int rndis_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
char *out = page;
|
||||
int len;
|
||||
rndis_params *param = (rndis_params *) data;
|
||||
rndis_params *param = m->private;
|
||||
|
||||
out += snprintf (out, count,
|
||||
seq_printf(m,
|
||||
"Config Nr. %d\n"
|
||||
"used : %s\n"
|
||||
"state : %s\n"
|
||||
@ -1326,25 +1076,13 @@ static int rndis_proc_read (char *page, char **start, off_t off, int count, int
|
||||
(param->media_state) ? 0 : param->speed*100,
|
||||
(param->media_state) ? "disconnected" : "connected",
|
||||
param->vendorID, param->vendorDescr);
|
||||
|
||||
len = out - page;
|
||||
len -= off;
|
||||
|
||||
if (len < count) {
|
||||
*eof = 1;
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
} else
|
||||
len = count;
|
||||
|
||||
*start = page + off;
|
||||
return len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rndis_proc_write (struct file *file, const char __user *buffer,
|
||||
unsigned long count, void *data)
|
||||
static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
rndis_params *p = data;
|
||||
rndis_params *p = PDE(file->f_path.dentry->d_inode)->data;
|
||||
u32 speed = 0;
|
||||
int i, fl_speed = 0;
|
||||
|
||||
@ -1386,6 +1124,20 @@ static int rndis_proc_write (struct file *file, const char __user *buffer,
|
||||
return count;
|
||||
}
|
||||
|
||||
static int rndis_proc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, rndis_proc_show, PDE(inode)->data);
|
||||
}
|
||||
|
||||
static const struct file_operations rndis_proc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rndis_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
.write = rndis_proc_write,
|
||||
};
|
||||
|
||||
#define NAME_TEMPLATE "driver/rndis-%03d"
|
||||
|
||||
static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS];
|
||||
@ -1403,7 +1155,9 @@ int __init rndis_init (void)
|
||||
|
||||
sprintf (name, NAME_TEMPLATE, i);
|
||||
if (!(rndis_connect_state [i]
|
||||
= create_proc_entry (name, 0660, NULL)))
|
||||
= proc_create_data(name, 0660, NULL,
|
||||
&rndis_proc_fops,
|
||||
(void *)(rndis_per_dev_params + i))))
|
||||
{
|
||||
DBG("%s :remove entries", __func__);
|
||||
while (i) {
|
||||
@ -1413,11 +1167,6 @@ int __init rndis_init (void)
|
||||
DBG("\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rndis_connect_state [i]->write_proc = rndis_proc_write;
|
||||
rndis_connect_state [i]->read_proc = rndis_proc_read;
|
||||
rndis_connect_state [i]->data = (void *)
|
||||
(rndis_per_dev_params + i);
|
||||
#endif
|
||||
rndis_per_dev_params [i].confignr = i;
|
||||
rndis_per_dev_params [i].used = 0;
|
||||
|
@ -1,8 +1,6 @@
|
||||
/*
|
||||
* RNDIS Definitions for Remote NDIS
|
||||
*
|
||||
* Version: $Id: rndis.h,v 1.15 2004/03/25 21:33:46 robert Exp $
|
||||
*
|
||||
* Authors: Benedikt Spranger, Pengutronix
|
||||
* Robert Schwebel, Pengutronix
|
||||
*
|
||||
@ -235,20 +233,19 @@ typedef struct rndis_params
|
||||
const u8 *host_mac;
|
||||
u16 *filter;
|
||||
struct net_device *dev;
|
||||
struct net_device_stats *stats;
|
||||
|
||||
u32 vendorID;
|
||||
const char *vendorDescr;
|
||||
int (*ack) (struct net_device *);
|
||||
void (*resp_avail)(void *v);
|
||||
void *v;
|
||||
struct list_head resp_queue;
|
||||
} rndis_params;
|
||||
|
||||
/* RNDIS Message parser and other useless functions */
|
||||
int rndis_msg_parser (u8 configNr, u8 *buf);
|
||||
int rndis_register (int (*rndis_control_ack) (struct net_device *));
|
||||
int rndis_register(void (*resp_avail)(void *v), void *v);
|
||||
void rndis_deregister (int configNr);
|
||||
int rndis_set_param_dev (u8 configNr, struct net_device *dev,
|
||||
struct net_device_stats *stats,
|
||||
u16 *cdc_filter);
|
||||
int rndis_set_param_vendor (u8 configNr, u32 vendorID,
|
||||
const char *vendorDescr);
|
||||
|
File diff suppressed because it is too large
Load Diff
967
drivers/usb/gadget/u_ether.c
Normal file
967
drivers/usb/gadget/u_ether.c
Normal file
@ -0,0 +1,967 @@
|
||||
/*
|
||||
* u_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
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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/utsname.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, "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.)
|
||||
*/
|
||||
|
||||
#define DRIVER_VERSION "29-May-2008"
|
||||
|
||||
struct eth_dev {
|
||||
/* lock is held while accessing port_usb
|
||||
* or updating its backlink port_usb->ioport
|
||||
*/
|
||||
spinlock_t lock;
|
||||
struct gether *port_usb;
|
||||
|
||||
struct net_device *net;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
spinlock_t req_lock; /* guard {rx,tx}_reqs */
|
||||
struct list_head tx_reqs, rx_reqs;
|
||||
atomic_t tx_qlen;
|
||||
|
||||
unsigned header_len;
|
||||
struct sk_buff *(*wrap)(struct sk_buff *skb);
|
||||
int (*unwrap)(struct sk_buff *skb);
|
||||
|
||||
struct work_struct work;
|
||||
|
||||
unsigned long todo;
|
||||
#define WORK_RX_MEMORY 0
|
||||
|
||||
bool zlp;
|
||||
u8 host_mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define RX_EXTRA 20 /* bytes guarding against rx overflows */
|
||||
|
||||
#define DEFAULT_QLEN 2 /* double buffering by default */
|
||||
|
||||
|
||||
#ifdef CONFIG_USB_GADGET_DUALSPEED
|
||||
|
||||
static unsigned qmult = 5;
|
||||
module_param(qmult, uint, S_IRUGO|S_IWUSR);
|
||||
MODULE_PARM_DESC(qmult, "queue length multiplier at high speed");
|
||||
|
||||
#else /* full speed (low speed doesn't do bulk) */
|
||||
#define qmult 1
|
||||
#endif
|
||||
|
||||
/* for dual-speed hardware, use deeper queues at highspeed */
|
||||
static inline int qlen(struct usb_gadget *gadget)
|
||||
{
|
||||
if (gadget_is_dualspeed(gadget) && gadget->speed == USB_SPEED_HIGH)
|
||||
return qmult * DEFAULT_QLEN;
|
||||
else
|
||||
return DEFAULT_QLEN;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* REVISIT there must be a better way than having two sets
|
||||
* of debug calls ...
|
||||
*/
|
||||
|
||||
#undef DBG
|
||||
#undef VDBG
|
||||
#undef ERROR
|
||||
#undef WARN
|
||||
#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 WARN(dev, fmt, args...) \
|
||||
xprintk(dev , KERN_WARNING , fmt , ## args)
|
||||
#define INFO(dev, fmt, args...) \
|
||||
xprintk(dev , KERN_INFO , fmt , ## args)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* NETWORK DRIVER HOOKUP (to the layer above this driver) */
|
||||
|
||||
static int eth_change_mtu(struct net_device *net, int new_mtu)
|
||||
{
|
||||
struct eth_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_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p)
|
||||
{
|
||||
struct eth_dev *dev = netdev_priv(net);
|
||||
|
||||
strlcpy(p->driver, "g_ether", sizeof p->driver);
|
||||
strlcpy(p->version, DRIVER_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 u32 eth_get_link(struct net_device *net)
|
||||
{
|
||||
struct eth_dev *dev = netdev_priv(net);
|
||||
return dev->gadget->speed != USB_SPEED_UNKNOWN;
|
||||
}
|
||||
|
||||
/* REVISIT can also support:
|
||||
* - WOL (by tracking suspends and issuing remote wakeup)
|
||||
* - msglevel (implies updated messaging)
|
||||
* - ... probably more ethtool ops
|
||||
*/
|
||||
|
||||
static struct ethtool_ops ops = {
|
||||
.get_drvinfo = eth_get_drvinfo,
|
||||
.get_link = eth_get_link
|
||||
};
|
||||
|
||||
static void defer_kevent(struct eth_dev *dev, int flag)
|
||||
{
|
||||
if (test_and_set_bit(flag, &dev->todo))
|
||||
return;
|
||||
if (!schedule_work(&dev->work))
|
||||
ERROR(dev, "kevent %d may have been dropped\n", flag);
|
||||
else
|
||||
DBG(dev, "kevent %d scheduled\n", flag);
|
||||
}
|
||||
|
||||
static void rx_complete(struct usb_ep *ep, struct usb_request *req);
|
||||
|
||||
static int
|
||||
rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int retval = -ENOMEM;
|
||||
size_t size = 0;
|
||||
struct usb_ep *out;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (dev->port_usb)
|
||||
out = dev->port_usb->out_ep;
|
||||
else
|
||||
out = NULL;
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
if (!out)
|
||||
return -ENOTCONN;
|
||||
|
||||
|
||||
/* Padding up to RX_EXTRA handles minor disagreements with host.
|
||||
* Normally we use the USB "terminate on short read" convention;
|
||||
* so allow up to (N*maxpacket), since that memory is normally
|
||||
* already allocated. Some hardware doesn't deal well with short
|
||||
* reads (e.g. DMA must be N*maxpacket), so for now don't trim a
|
||||
* byte off the end (to force hardware errors on overflow).
|
||||
*
|
||||
* RNDIS uses internal framing, and explicitly allows senders to
|
||||
* pad to end-of-packet. That's potentially nice for speed, but
|
||||
* means receivers can't recover lost synch on their own (because
|
||||
* new packets don't only start after a short RX).
|
||||
*/
|
||||
size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA;
|
||||
size += dev->port_usb->header_len;
|
||||
size += out->maxpacket - 1;
|
||||
size -= size % out->maxpacket;
|
||||
|
||||
skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags);
|
||||
if (skb == NULL) {
|
||||
DBG(dev, "no rx skb\n");
|
||||
goto enomem;
|
||||
}
|
||||
|
||||
/* Some platforms perform better when IP packets are aligned,
|
||||
* but on at least one, checksumming fails otherwise. Note:
|
||||
* RNDIS headers involve variable numbers of LE32 values.
|
||||
*/
|
||||
skb_reserve(skb, NET_IP_ALIGN);
|
||||
|
||||
req->buf = skb->data;
|
||||
req->length = size;
|
||||
req->complete = rx_complete;
|
||||
req->context = skb;
|
||||
|
||||
retval = usb_ep_queue(out, req, gfp_flags);
|
||||
if (retval == -ENOMEM)
|
||||
enomem:
|
||||
defer_kevent(dev, WORK_RX_MEMORY);
|
||||
if (retval) {
|
||||
DBG(dev, "rx submit --> %d\n", retval);
|
||||
if (skb)
|
||||
dev_kfree_skb_any(skb);
|
||||
spin_lock_irqsave(&dev->req_lock, flags);
|
||||
list_add(&req->list, &dev->rx_reqs);
|
||||
spin_unlock_irqrestore(&dev->req_lock, flags);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void rx_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct sk_buff *skb = req->context;
|
||||
struct eth_dev *dev = ep->driver_data;
|
||||
int status = req->status;
|
||||
|
||||
switch (status) {
|
||||
|
||||
/* normal completion */
|
||||
case 0:
|
||||
skb_put(skb, req->actual);
|
||||
if (dev->unwrap)
|
||||
status = dev->unwrap(skb);
|
||||
if (status < 0
|
||||
|| ETH_HLEN > skb->len
|
||||
|| skb->len > ETH_FRAME_LEN) {
|
||||
dev->net->stats.rx_errors++;
|
||||
dev->net->stats.rx_length_errors++;
|
||||
DBG(dev, "rx length %d\n", skb->len);
|
||||
break;
|
||||
}
|
||||
|
||||
skb->protocol = eth_type_trans(skb, dev->net);
|
||||
dev->net->stats.rx_packets++;
|
||||
dev->net->stats.rx_bytes += skb->len;
|
||||
|
||||
/* no buffer copies needed, unless hardware can't
|
||||
* use skb buffers.
|
||||
*/
|
||||
status = netif_rx(skb);
|
||||
skb = NULL;
|
||||
break;
|
||||
|
||||
/* software-driven interface shutdown */
|
||||
case -ECONNRESET: /* unlink */
|
||||
case -ESHUTDOWN: /* disconnect etc */
|
||||
VDBG(dev, "rx shutdown, code %d\n", status);
|
||||
goto quiesce;
|
||||
|
||||
/* for hardware automagic (such as pxa) */
|
||||
case -ECONNABORTED: /* endpoint reset */
|
||||
DBG(dev, "rx %s reset\n", ep->name);
|
||||
defer_kevent(dev, WORK_RX_MEMORY);
|
||||
quiesce:
|
||||
dev_kfree_skb_any(skb);
|
||||
goto clean;
|
||||
|
||||
/* data overrun */
|
||||
case -EOVERFLOW:
|
||||
dev->net->stats.rx_over_errors++;
|
||||
/* FALLTHROUGH */
|
||||
|
||||
default:
|
||||
dev->net->stats.rx_errors++;
|
||||
DBG(dev, "rx status %d\n", status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (skb)
|
||||
dev_kfree_skb_any(skb);
|
||||
if (!netif_running(dev->net)) {
|
||||
clean:
|
||||
spin_lock(&dev->req_lock);
|
||||
list_add(&req->list, &dev->rx_reqs);
|
||||
spin_unlock(&dev->req_lock);
|
||||
req = NULL;
|
||||
}
|
||||
if (req)
|
||||
rx_submit(dev, req, GFP_ATOMIC);
|
||||
}
|
||||
|
||||
static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n)
|
||||
{
|
||||
unsigned i;
|
||||
struct usb_request *req;
|
||||
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
|
||||
/* queue/recycle up to N requests */
|
||||
i = n;
|
||||
list_for_each_entry(req, list, list) {
|
||||
if (i-- == 0)
|
||||
goto extra;
|
||||
}
|
||||
while (i--) {
|
||||
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
|
||||
if (!req)
|
||||
return list_empty(list) ? -ENOMEM : 0;
|
||||
list_add(&req->list, list);
|
||||
}
|
||||
return 0;
|
||||
|
||||
extra:
|
||||
/* free extras */
|
||||
for (;;) {
|
||||
struct list_head *next;
|
||||
|
||||
next = req->list.next;
|
||||
list_del(&req->list);
|
||||
usb_ep_free_request(ep, req);
|
||||
|
||||
if (next == list)
|
||||
break;
|
||||
|
||||
req = container_of(next, struct usb_request, list);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alloc_requests(struct eth_dev *dev, struct gether *link, unsigned n)
|
||||
{
|
||||
int status;
|
||||
|
||||
spin_lock(&dev->req_lock);
|
||||
status = prealloc(&dev->tx_reqs, link->in_ep, n);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
status = prealloc(&dev->rx_reqs, link->out_ep, n);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
goto done;
|
||||
fail:
|
||||
DBG(dev, "can't alloc requests\n");
|
||||
done:
|
||||
spin_unlock(&dev->req_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
|
||||
{
|
||||
struct usb_request *req;
|
||||
unsigned long flags;
|
||||
|
||||
/* fill unused rxq slots with some skb */
|
||||
spin_lock_irqsave(&dev->req_lock, flags);
|
||||
while (!list_empty(&dev->rx_reqs)) {
|
||||
req = container_of(dev->rx_reqs.next,
|
||||
struct usb_request, list);
|
||||
list_del_init(&req->list);
|
||||
spin_unlock_irqrestore(&dev->req_lock, flags);
|
||||
|
||||
if (rx_submit(dev, req, gfp_flags) < 0) {
|
||||
defer_kevent(dev, WORK_RX_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dev->req_lock, flags);
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->req_lock, flags);
|
||||
}
|
||||
|
||||
static void eth_work(struct work_struct *work)
|
||||
{
|
||||
struct eth_dev *dev = container_of(work, struct eth_dev, work);
|
||||
|
||||
if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) {
|
||||
if (netif_running(dev->net))
|
||||
rx_fill(dev, GFP_KERNEL);
|
||||
}
|
||||
|
||||
if (dev->todo)
|
||||
DBG(dev, "work done, flags = 0x%lx\n", dev->todo);
|
||||
}
|
||||
|
||||
static void tx_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct sk_buff *skb = req->context;
|
||||
struct eth_dev *dev = ep->driver_data;
|
||||
|
||||
switch (req->status) {
|
||||
default:
|
||||
dev->net->stats.tx_errors++;
|
||||
VDBG(dev, "tx err %d\n", req->status);
|
||||
/* FALLTHROUGH */
|
||||
case -ECONNRESET: /* unlink */
|
||||
case -ESHUTDOWN: /* disconnect etc */
|
||||
break;
|
||||
case 0:
|
||||
dev->net->stats.tx_bytes += skb->len;
|
||||
}
|
||||
dev->net->stats.tx_packets++;
|
||||
|
||||
spin_lock(&dev->req_lock);
|
||||
list_add(&req->list, &dev->tx_reqs);
|
||||
spin_unlock(&dev->req_lock);
|
||||
dev_kfree_skb_any(skb);
|
||||
|
||||
atomic_dec(&dev->tx_qlen);
|
||||
if (netif_carrier_ok(dev->net))
|
||||
netif_wake_queue(dev->net);
|
||||
}
|
||||
|
||||
static inline int is_promisc(u16 cdc_filter)
|
||||
{
|
||||
return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS;
|
||||
}
|
||||
|
||||
static int eth_start_xmit(struct sk_buff *skb, struct net_device *net)
|
||||
{
|
||||
struct eth_dev *dev = netdev_priv(net);
|
||||
int length = skb->len;
|
||||
int retval;
|
||||
struct usb_request *req = NULL;
|
||||
unsigned long flags;
|
||||
struct usb_ep *in;
|
||||
u16 cdc_filter;
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (dev->port_usb) {
|
||||
in = dev->port_usb->in_ep;
|
||||
cdc_filter = dev->port_usb->cdc_filter;
|
||||
} else {
|
||||
in = NULL;
|
||||
cdc_filter = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
if (!in) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* apply outgoing CDC or RNDIS filters */
|
||||
if (!is_promisc(cdc_filter)) {
|
||||
u8 *dest = skb->data;
|
||||
|
||||
if (is_multicast_ether_addr(dest)) {
|
||||
u16 type;
|
||||
|
||||
/* ignores USB_CDC_PACKET_TYPE_MULTICAST and host
|
||||
* SET_ETHERNET_MULTICAST_FILTERS requests
|
||||
*/
|
||||
if (is_broadcast_ether_addr(dest))
|
||||
type = USB_CDC_PACKET_TYPE_BROADCAST;
|
||||
else
|
||||
type = USB_CDC_PACKET_TYPE_ALL_MULTICAST;
|
||||
if (!(cdc_filter & type)) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* ignores USB_CDC_PACKET_TYPE_DIRECTED */
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dev->req_lock, flags);
|
||||
/*
|
||||
* this freelist can be empty if an interrupt triggered disconnect()
|
||||
* and reconfigured the gadget (shutting down this queue) after the
|
||||
* network stack decided to xmit but before we got the spinlock.
|
||||
*/
|
||||
if (list_empty(&dev->tx_reqs)) {
|
||||
spin_unlock_irqrestore(&dev->req_lock, flags);
|
||||
return 1;
|
||||
}
|
||||
|
||||
req = container_of(dev->tx_reqs.next, struct usb_request, list);
|
||||
list_del(&req->list);
|
||||
|
||||
/* temporarily stop TX queue when the freelist empties */
|
||||
if (list_empty(&dev->tx_reqs))
|
||||
netif_stop_queue(net);
|
||||
spin_unlock_irqrestore(&dev->req_lock, flags);
|
||||
|
||||
/* no buffer copies needed, unless the network stack did it
|
||||
* or the hardware can't use skb buffers.
|
||||
* or there's not enough space for extra headers we need
|
||||
*/
|
||||
if (dev->wrap) {
|
||||
struct sk_buff *skb_new;
|
||||
|
||||
skb_new = dev->wrap(skb);
|
||||
if (!skb_new)
|
||||
goto drop;
|
||||
|
||||
dev_kfree_skb_any(skb);
|
||||
skb = skb_new;
|
||||
length = skb->len;
|
||||
}
|
||||
req->buf = skb->data;
|
||||
req->context = skb;
|
||||
req->complete = tx_complete;
|
||||
|
||||
/* use zlp framing on tx for strict CDC-Ether conformance,
|
||||
* though any robust network rx path ignores extra padding.
|
||||
* and some hardware doesn't like to write zlps.
|
||||
*/
|
||||
req->zero = 1;
|
||||
if (!dev->zlp && (length % in->maxpacket) == 0)
|
||||
length++;
|
||||
|
||||
req->length = length;
|
||||
|
||||
/* throttle highspeed IRQ rate back slightly */
|
||||
if (gadget_is_dualspeed(dev->gadget))
|
||||
req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH)
|
||||
? ((atomic_read(&dev->tx_qlen) % qmult) != 0)
|
||||
: 0;
|
||||
|
||||
retval = usb_ep_queue(in, req, GFP_ATOMIC);
|
||||
switch (retval) {
|
||||
default:
|
||||
DBG(dev, "tx queue err %d\n", retval);
|
||||
break;
|
||||
case 0:
|
||||
net->trans_start = jiffies;
|
||||
atomic_inc(&dev->tx_qlen);
|
||||
}
|
||||
|
||||
if (retval) {
|
||||
drop:
|
||||
dev->net->stats.tx_dropped++;
|
||||
dev_kfree_skb_any(skb);
|
||||
spin_lock_irqsave(&dev->req_lock, flags);
|
||||
if (list_empty(&dev->tx_reqs))
|
||||
netif_start_queue(net);
|
||||
list_add(&req->list, &dev->tx_reqs);
|
||||
spin_unlock_irqrestore(&dev->req_lock, flags);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
|
||||
{
|
||||
DBG(dev, "%s\n", __func__);
|
||||
|
||||
/* fill the rx queue */
|
||||
rx_fill(dev, gfp_flags);
|
||||
|
||||
/* and open the tx floodgates */
|
||||
atomic_set(&dev->tx_qlen, 0);
|
||||
netif_wake_queue(dev->net);
|
||||
}
|
||||
|
||||
static int eth_open(struct net_device *net)
|
||||
{
|
||||
struct eth_dev *dev = netdev_priv(net);
|
||||
struct gether *link;
|
||||
|
||||
DBG(dev, "%s\n", __func__);
|
||||
if (netif_carrier_ok(dev->net))
|
||||
eth_start(dev, GFP_KERNEL);
|
||||
|
||||
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_stop(struct net_device *net)
|
||||
{
|
||||
struct eth_dev *dev = netdev_priv(net);
|
||||
unsigned long flags;
|
||||
|
||||
VDBG(dev, "%s\n", __func__);
|
||||
netif_stop_queue(net);
|
||||
|
||||
DBG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n",
|
||||
dev->net->stats.rx_packets, dev->net->stats.tx_packets,
|
||||
dev->net->stats.rx_errors, dev->net->stats.tx_errors
|
||||
);
|
||||
|
||||
/* ensure there are no more active requests */
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (dev->port_usb) {
|
||||
struct gether *link = dev->port_usb;
|
||||
|
||||
if (link->close)
|
||||
link->close(link);
|
||||
|
||||
/* NOTE: we have no abort-queue primitive we could use
|
||||
* to cancel all pending I/O. Instead, we disable then
|
||||
* reenable the endpoints ... this idiom may leave toggle
|
||||
* wrong, but that's a self-correcting error.
|
||||
*
|
||||
* REVISIT: we *COULD* just let the transfers complete at
|
||||
* their own pace; the network stack can handle old packets.
|
||||
* For the moment we leave this here, since it works.
|
||||
*/
|
||||
usb_ep_disable(link->in_ep);
|
||||
usb_ep_disable(link->out_ep);
|
||||
if (netif_carrier_ok(net)) {
|
||||
DBG(dev, "host still using in/out endpoints\n");
|
||||
usb_ep_enable(link->in_ep, link->in);
|
||||
usb_ep_enable(link->out_ep, link->out);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */
|
||||
static char *dev_addr;
|
||||
module_param(dev_addr, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev_addr, "Device Ethernet Address");
|
||||
|
||||
/* this address is invisible to ifconfig */
|
||||
static char *host_addr;
|
||||
module_param(host_addr, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(host_addr, "Host Ethernet Address");
|
||||
|
||||
|
||||
static u8 __init nibble(unsigned char c)
|
||||
{
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
c = toupper(c);
|
||||
if (isxdigit(c))
|
||||
return 10 + c - 'A';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init get_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 = nibble(*str++) << 4;
|
||||
num |= (nibble(*str++));
|
||||
dev_addr [i] = num;
|
||||
}
|
||||
if (is_valid_ether_addr(dev_addr))
|
||||
return 0;
|
||||
}
|
||||
random_ether_addr(dev_addr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct eth_dev *the_dev;
|
||||
|
||||
|
||||
/**
|
||||
* gether_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 __init gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN])
|
||||
{
|
||||
struct eth_dev *dev;
|
||||
struct net_device *net;
|
||||
int status;
|
||||
|
||||
if (the_dev)
|
||||
return -EBUSY;
|
||||
|
||||
net = alloc_etherdev(sizeof *dev);
|
||||
if (!net)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = netdev_priv(net);
|
||||
spin_lock_init(&dev->lock);
|
||||
spin_lock_init(&dev->req_lock);
|
||||
INIT_WORK(&dev->work, eth_work);
|
||||
INIT_LIST_HEAD(&dev->tx_reqs);
|
||||
INIT_LIST_HEAD(&dev->rx_reqs);
|
||||
|
||||
/* network device setup */
|
||||
dev->net = net;
|
||||
strcpy(net->name, "usb%d");
|
||||
|
||||
if (get_ether_addr(dev_addr, net->dev_addr))
|
||||
dev_warn(&g->dev,
|
||||
"using random %s ethernet address\n", "self");
|
||||
if (get_ether_addr(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->change_mtu = eth_change_mtu;
|
||||
net->hard_start_xmit = eth_start_xmit;
|
||||
net->open = eth_open;
|
||||
net->stop = eth_stop;
|
||||
/* watchdog_timeo, tx_timeout ... */
|
||||
/* set_multicast_list */
|
||||
SET_ETHTOOL_OPS(net, &ops);
|
||||
|
||||
/* two kinds of host-initiated state changes:
|
||||
* - iff DATA transfer is active, carrier is "on"
|
||||
* - tx queueing enabled if open *and* carrier is "on"
|
||||
*/
|
||||
netif_stop_queue(net);
|
||||
netif_carrier_off(net);
|
||||
|
||||
dev->gadget = g;
|
||||
SET_NETDEV_DEV(net, &g->dev);
|
||||
|
||||
status = register_netdev(net);
|
||||
if (status < 0) {
|
||||
dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
|
||||
free_netdev(net);
|
||||
} else {
|
||||
DECLARE_MAC_BUF(tmp);
|
||||
|
||||
INFO(dev, "MAC %s\n", print_mac(tmp, net->dev_addr));
|
||||
INFO(dev, "HOST MAC %s\n", print_mac(tmp, dev->host_mac));
|
||||
|
||||
the_dev = dev;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_cleanup - remove Ethernet-over-USB device
|
||||
* Context: may sleep
|
||||
*
|
||||
* This is called to free all resources allocated by @gether_setup().
|
||||
*/
|
||||
void gether_cleanup(void)
|
||||
{
|
||||
if (!the_dev)
|
||||
return;
|
||||
|
||||
unregister_netdev(the_dev->net);
|
||||
free_netdev(the_dev->net);
|
||||
|
||||
/* assuming we used keventd, it must quiesce too */
|
||||
flush_scheduled_work();
|
||||
|
||||
the_dev = NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* gether_connect - 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.
|
||||
* Context: irqs blocked
|
||||
*
|
||||
* This is called to activate endpoints and let the network layer know
|
||||
* the connection is active ("carrier detect"). It may cause the I/O
|
||||
* queues to open and start letting network packets flow, but will in
|
||||
* any case activate the endpoints so that they respond properly to the
|
||||
* USB host.
|
||||
*
|
||||
* Verify net_device pointer returned using IS_ERR(). If it doesn't
|
||||
* indicate some error code (negative errno), ep->driver_data values
|
||||
* have been overwritten.
|
||||
*/
|
||||
struct net_device *gether_connect(struct gether *link)
|
||||
{
|
||||
struct eth_dev *dev = the_dev;
|
||||
int result = 0;
|
||||
|
||||
if (!dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
link->in_ep->driver_data = dev;
|
||||
result = usb_ep_enable(link->in_ep, link->in);
|
||||
if (result != 0) {
|
||||
DBG(dev, "enable %s --> %d\n",
|
||||
link->in_ep->name, result);
|
||||
goto fail0;
|
||||
}
|
||||
|
||||
link->out_ep->driver_data = dev;
|
||||
result = usb_ep_enable(link->out_ep, link->out);
|
||||
if (result != 0) {
|
||||
DBG(dev, "enable %s --> %d\n",
|
||||
link->out_ep->name, result);
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
result = alloc_requests(dev, link, qlen(dev->gadget));
|
||||
|
||||
if (result == 0) {
|
||||
dev->zlp = link->is_zlp_ok;
|
||||
DBG(dev, "qlen %d\n", qlen(dev->gadget));
|
||||
|
||||
dev->header_len = link->header_len;
|
||||
dev->unwrap = link->unwrap;
|
||||
dev->wrap = link->wrap;
|
||||
|
||||
spin_lock(&dev->lock);
|
||||
dev->port_usb = link;
|
||||
link->ioport = dev;
|
||||
spin_unlock(&dev->lock);
|
||||
|
||||
netif_carrier_on(dev->net);
|
||||
if (netif_running(dev->net))
|
||||
eth_start(dev, GFP_ATOMIC);
|
||||
|
||||
/* on error, disable any endpoints */
|
||||
} else {
|
||||
(void) usb_ep_disable(link->out_ep);
|
||||
fail1:
|
||||
(void) usb_ep_disable(link->in_ep);
|
||||
}
|
||||
fail0:
|
||||
/* caller is responsible for cleanup on error */
|
||||
if (result < 0)
|
||||
return ERR_PTR(result);
|
||||
return dev->net;
|
||||
}
|
||||
|
||||
/**
|
||||
* gether_disconnect - notify network layer that USB link is inactive
|
||||
* @link: the USB link, on which gether_connect() was called
|
||||
* Context: irqs blocked
|
||||
*
|
||||
* This is called to deactivate endpoints and let the network layer know
|
||||
* the connection went inactive ("no carrier").
|
||||
*
|
||||
* On return, the state is as if gether_connect() had never been called.
|
||||
* The endpoints are inactive, and accordingly without active USB I/O.
|
||||
* Pointers to endpoint descriptors and endpoint private data are nulled.
|
||||
*/
|
||||
void gether_disconnect(struct gether *link)
|
||||
{
|
||||
struct eth_dev *dev = link->ioport;
|
||||
struct usb_request *req;
|
||||
|
||||
WARN_ON(!dev);
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
DBG(dev, "%s\n", __func__);
|
||||
|
||||
netif_stop_queue(dev->net);
|
||||
netif_carrier_off(dev->net);
|
||||
|
||||
/* disable endpoints, forcing (synchronous) completion
|
||||
* of all pending i/o. then free the request objects
|
||||
* and forget about the endpoints.
|
||||
*/
|
||||
usb_ep_disable(link->in_ep);
|
||||
spin_lock(&dev->req_lock);
|
||||
while (!list_empty(&dev->tx_reqs)) {
|
||||
req = container_of(dev->tx_reqs.next,
|
||||
struct usb_request, list);
|
||||
list_del(&req->list);
|
||||
|
||||
spin_unlock(&dev->req_lock);
|
||||
usb_ep_free_request(link->in_ep, req);
|
||||
spin_lock(&dev->req_lock);
|
||||
}
|
||||
spin_unlock(&dev->req_lock);
|
||||
link->in_ep->driver_data = NULL;
|
||||
link->in = NULL;
|
||||
|
||||
usb_ep_disable(link->out_ep);
|
||||
spin_lock(&dev->req_lock);
|
||||
while (!list_empty(&dev->rx_reqs)) {
|
||||
req = container_of(dev->rx_reqs.next,
|
||||
struct usb_request, list);
|
||||
list_del(&req->list);
|
||||
|
||||
spin_unlock(&dev->req_lock);
|
||||
usb_ep_free_request(link->out_ep, req);
|
||||
spin_lock(&dev->req_lock);
|
||||
}
|
||||
spin_unlock(&dev->req_lock);
|
||||
link->out_ep->driver_data = NULL;
|
||||
link->out = NULL;
|
||||
|
||||
/* finish forgetting about this USB link episode */
|
||||
dev->header_len = 0;
|
||||
dev->unwrap = NULL;
|
||||
dev->wrap = NULL;
|
||||
|
||||
spin_lock(&dev->lock);
|
||||
dev->port_usb = NULL;
|
||||
link->ioport = NULL;
|
||||
spin_unlock(&dev->lock);
|
||||
}
|
127
drivers/usb/gadget/u_ether.h
Normal file
127
drivers/usb/gadget/u_ether.h
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* u_ether.h -- interface to USB gadget "ethernet link" utilities
|
||||
*
|
||||
* Copyright (C) 2003-2005,2008 David Brownell
|
||||
* Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef __U_ETHER_H
|
||||
#define __U_ETHER_H
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
|
||||
#include "gadget_chips.h"
|
||||
|
||||
|
||||
/*
|
||||
* This represents the USB side of an "ethernet" link, managed by a USB
|
||||
* function which provides control and (maybe) framing. Two functions
|
||||
* in different configurations could share the same ethernet link/netdev,
|
||||
* using different host interaction models.
|
||||
*
|
||||
* There is a current limitation that only one instance of this link may
|
||||
* be present in any given configuration. When that's a problem, network
|
||||
* layer facilities can be used to package multiple logical links on this
|
||||
* single "physical" one.
|
||||
*/
|
||||
struct gether {
|
||||
struct usb_function func;
|
||||
|
||||
/* updated by gether_{connect,disconnect} */
|
||||
struct eth_dev *ioport;
|
||||
|
||||
/* endpoints handle full and/or high speeds */
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
|
||||
/* descriptors match device speed at gether_connect() time */
|
||||
struct usb_endpoint_descriptor *in;
|
||||
struct usb_endpoint_descriptor *out;
|
||||
|
||||
bool is_zlp_ok;
|
||||
|
||||
u16 cdc_filter;
|
||||
|
||||
/* hooks for added framing, as needed for RNDIS and EEM.
|
||||
* we currently don't support multiple frames per SKB.
|
||||
*/
|
||||
u32 header_len;
|
||||
struct sk_buff *(*wrap)(struct sk_buff *skb);
|
||||
int (*unwrap)(struct sk_buff *skb);
|
||||
|
||||
/* called on network open/close */
|
||||
void (*open)(struct gether *);
|
||||
void (*close)(struct gether *);
|
||||
};
|
||||
|
||||
#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \
|
||||
|USB_CDC_PACKET_TYPE_ALL_MULTICAST \
|
||||
|USB_CDC_PACKET_TYPE_PROMISCUOUS \
|
||||
|USB_CDC_PACKET_TYPE_DIRECTED)
|
||||
|
||||
|
||||
/* netdev setup/teardown as directed by the gadget driver */
|
||||
int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]);
|
||||
void gether_cleanup(void);
|
||||
|
||||
/* connect/disconnect is handled by individual functions */
|
||||
struct net_device *gether_connect(struct gether *);
|
||||
void gether_disconnect(struct gether *);
|
||||
|
||||
/* Some controllers can't support CDC Ethernet (ECM) ... */
|
||||
static inline bool can_support_ecm(struct usb_gadget *gadget)
|
||||
{
|
||||
if (!gadget_supports_altsettings(gadget))
|
||||
return false;
|
||||
|
||||
/* SA1100 can do ECM, *without* status endpoint ... but we'll
|
||||
* only use it in non-ECM mode for backwards compatibility
|
||||
* (and since we currently require a status endpoint)
|
||||
*/
|
||||
if (gadget_is_sa1100(gadget))
|
||||
return false;
|
||||
|
||||
/* Everything else is *presumably* fine ... but this is a bit
|
||||
* chancy, so be **CERTAIN** there are no hardware issues with
|
||||
* your controller. Add it above if it can't handle CDC.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
/* each configuration may bind one instance of an ethernet link */
|
||||
int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
|
||||
int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
|
||||
|
||||
#ifdef CONFIG_USB_ETH_RNDIS
|
||||
|
||||
int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
|
||||
|
||||
#else
|
||||
|
||||
static inline int
|
||||
rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* __U_ETHER_H */
|
1246
drivers/usb/gadget/u_serial.c
Normal file
1246
drivers/usb/gadget/u_serial.c
Normal file
File diff suppressed because it is too large
Load Diff
58
drivers/usb/gadget/u_serial.h
Normal file
58
drivers/usb/gadget/u_serial.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* u_serial.h - interface to USB gadget "serial port"/TTY utilities
|
||||
*
|
||||
* Copyright (C) 2008 David Brownell
|
||||
* Copyright (C) 2008 by Nokia Corporation
|
||||
*
|
||||
* This software is distributed under the terms of the GNU General
|
||||
* Public License ("GPL") as published by the Free Software Foundation,
|
||||
* either version 2 of that License or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __U_SERIAL_H
|
||||
#define __U_SERIAL_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/cdc.h>
|
||||
|
||||
/*
|
||||
* One non-multiplexed "serial" I/O port ... there can be several of these
|
||||
* on any given USB peripheral device, if it provides enough endpoints.
|
||||
*
|
||||
* The "u_serial" utility component exists to do one thing: manage TTY
|
||||
* style I/O using the USB peripheral endpoints listed here, including
|
||||
* hookups to sysfs and /dev for each logical "tty" device.
|
||||
*
|
||||
* REVISIT need TTY --> USB event flow too, so ACM can report open/close
|
||||
* as carrier detect events. Model after ECM. There's more ACM state too.
|
||||
*
|
||||
* REVISIT someday, allow multiplexing several TTYs over these endpoints.
|
||||
*/
|
||||
struct gserial {
|
||||
struct usb_function func;
|
||||
|
||||
/* port is managed by gserial_{connect,disconnect} */
|
||||
struct gs_port *ioport;
|
||||
|
||||
struct usb_ep *in;
|
||||
struct usb_ep *out;
|
||||
struct usb_endpoint_descriptor *in_desc;
|
||||
struct usb_endpoint_descriptor *out_desc;
|
||||
|
||||
/* REVISIT avoid this CDC-ACM support harder ... */
|
||||
struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */
|
||||
};
|
||||
|
||||
/* port setup/teardown is handled by gadget driver */
|
||||
int gserial_setup(struct usb_gadget *g, unsigned n_ports);
|
||||
void gserial_cleanup(void);
|
||||
|
||||
/* connect/disconnect is handled by individual functions */
|
||||
int gserial_connect(struct gserial *, u8 port_num);
|
||||
void gserial_disconnect(struct gserial *);
|
||||
|
||||
/* functions are bound to configurations by a config or gadget driver */
|
||||
int acm_bind_config(struct usb_configuration *c, u8 port_num);
|
||||
int gser_bind_config(struct usb_configuration *c, u8 port_num);
|
||||
|
||||
#endif /* __U_SERIAL_H */
|
File diff suppressed because it is too large
Load Diff
@ -19,178 +19,66 @@
|
||||
#define USB_MCFG_RDCOMB (1<<30)
|
||||
#define USB_MCFG_SSDEN (1<<23)
|
||||
#define USB_MCFG_PHYPLLEN (1<<19)
|
||||
#define USB_MCFG_UCECLKEN (1<<18)
|
||||
#define USB_MCFG_EHCCLKEN (1<<17)
|
||||
#ifdef CONFIG_DMA_COHERENT
|
||||
#define USB_MCFG_UCAM (1<<7)
|
||||
#else
|
||||
#define USB_MCFG_UCAM (0)
|
||||
#endif
|
||||
#define USB_MCFG_EBMEN (1<<3)
|
||||
#define USB_MCFG_EMEMEN (1<<2)
|
||||
|
||||
#define USBH_ENABLE_CE (USB_MCFG_PHYPLLEN | USB_MCFG_EHCCLKEN)
|
||||
#define USBH_ENABLE_CE (USB_MCFG_PHYPLLEN | USB_MCFG_EHCCLKEN)
|
||||
#define USBH_ENABLE_INIT (USB_MCFG_PFEN | USB_MCFG_RDCOMB | \
|
||||
USBH_ENABLE_CE | USB_MCFG_SSDEN | \
|
||||
USB_MCFG_UCAM | USB_MCFG_EBMEN | \
|
||||
USB_MCFG_EMEMEN)
|
||||
|
||||
#ifdef CONFIG_DMA_COHERENT
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE \
|
||||
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
|
||||
| USB_MCFG_SSDEN | USB_MCFG_UCAM \
|
||||
| USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
|
||||
#else
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE \
|
||||
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
|
||||
| USB_MCFG_SSDEN \
|
||||
| USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
|
||||
#endif
|
||||
#define USBH_DISABLE (USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
|
||||
|
||||
extern int usb_disabled(void);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void au1xxx_start_ehc(struct platform_device *dev)
|
||||
static void au1xxx_start_ehc(void)
|
||||
{
|
||||
pr_debug(__FILE__ ": starting Au1xxx EHCI USB Controller\n");
|
||||
|
||||
/* write HW defaults again in case Yamon cleared them */
|
||||
if (au_readl(USB_HOST_CONFIG) == 0) {
|
||||
au_writel(0x00d02000, USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
}
|
||||
/* enable host controller */
|
||||
au_writel(USBH_ENABLE_CE | au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
au_writel(USBH_ENABLE_INIT | au_readl(USB_HOST_CONFIG),
|
||||
USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
/* enable clock to EHCI block and HS PHY PLL*/
|
||||
au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
|
||||
pr_debug(__FILE__ ": Clock to USB host has been enabled\n");
|
||||
/* enable EHCI mmio */
|
||||
au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
static void au1xxx_stop_ehc(struct platform_device *dev)
|
||||
static void au1xxx_stop_ehc(void)
|
||||
{
|
||||
pr_debug(__FILE__ ": stopping Au1xxx EHCI USB Controller\n");
|
||||
unsigned long c;
|
||||
|
||||
/* Disable mem */
|
||||
au_writel(~USBH_DISABLE & au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
|
||||
au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_DISABLE, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
/* Disable clock */
|
||||
au_writel(~USB_MCFG_EHCCLKEN & au_readl(USB_HOST_CONFIG),
|
||||
USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
|
||||
/* Disable EHC clock. If the HS PHY is unused disable it too. */
|
||||
c = au_readl(USB_HOST_CONFIG) & ~USB_MCFG_EHCCLKEN;
|
||||
if (!(c & USB_MCFG_UCECLKEN)) /* UDC disabled? */
|
||||
c &= ~USB_MCFG_PHYPLLEN; /* yes: disable HS PHY PLL */
|
||||
au_writel(c, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
/**
|
||||
* usb_ehci_au1xxx_probe - initialize Au1xxx-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
*/
|
||||
int usb_ehci_au1xxx_probe(const struct hc_driver *driver,
|
||||
struct usb_hcd **hcd_out, struct platform_device *dev)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
|
||||
#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
|
||||
|
||||
/* Au1200 AB USB does not support coherent memory */
|
||||
if (!(read_c0_prid() & 0xff)) {
|
||||
pr_info("%s: this is chip revision AB!\n", dev->name);
|
||||
pr_info("%s: update your board or re-configure the kernel\n",
|
||||
dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
au1xxx_start_ehc(dev);
|
||||
|
||||
if (dev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug("resource[1] is not IORESOURCE_IRQ");
|
||||
retval = -ENOMEM;
|
||||
}
|
||||
hcd = usb_create_hcd(driver, &dev->dev, "Au1xxx");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = dev->resource[0].start;
|
||||
hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed");
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ehci = hcd_to_ehci(hcd);
|
||||
ehci->caps = hcd->regs;
|
||||
ehci->regs = hcd->regs + HC_LENGTH(readl(&ehci->caps->hc_capbase));
|
||||
/* cache this readonly data; minimize chip reads */
|
||||
ehci->hcs_params = readl(&ehci->caps->hcs_params);
|
||||
|
||||
/* ehci_hcd_init(hcd_to_ehci(hcd)); */
|
||||
|
||||
retval =
|
||||
usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED | IRQF_SHARED);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
au1xxx_stop_ehc(dev);
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_ehci_hcd_au1xxx_remove - shutdown processing for Au1xxx-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_ehci_hcd_au1xxx_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
void usb_ehci_au1xxx_remove(struct usb_hcd *hcd, struct platform_device *dev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
au1xxx_stop_ehc(dev);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ehci_au1xxx_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "Au1xxx EHCI",
|
||||
.hcd_priv_size = sizeof(struct ehci_hcd),
|
||||
.description = hcd_name,
|
||||
.product_desc = "Au1xxx EHCI",
|
||||
.hcd_priv_size = sizeof(struct ehci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ehci_irq,
|
||||
.flags = HCD_MEMORY | HCD_USB2,
|
||||
.irq = ehci_irq,
|
||||
.flags = HCD_MEMORY | HCD_USB2,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
@ -198,48 +86,98 @@ static const struct hc_driver ehci_au1xxx_hc_driver = {
|
||||
* FIXME -- ehci_init() doesn't do enough here.
|
||||
* See ehci-ppc-soc for a complete implementation.
|
||||
*/
|
||||
.reset = ehci_init,
|
||||
.start = ehci_run,
|
||||
.stop = ehci_stop,
|
||||
.shutdown = ehci_shutdown,
|
||||
.reset = ehci_init,
|
||||
.start = ehci_run,
|
||||
.stop = ehci_stop,
|
||||
.shutdown = ehci_shutdown,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ehci_urb_enqueue,
|
||||
.urb_dequeue = ehci_urb_dequeue,
|
||||
.endpoint_disable = ehci_endpoint_disable,
|
||||
.urb_enqueue = ehci_urb_enqueue,
|
||||
.urb_dequeue = ehci_urb_dequeue,
|
||||
.endpoint_disable = ehci_endpoint_disable,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ehci_get_frame,
|
||||
.get_frame_number = ehci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ehci_hub_status_data,
|
||||
.hub_control = ehci_hub_control,
|
||||
.bus_suspend = ehci_bus_suspend,
|
||||
.bus_resume = ehci_bus_resume,
|
||||
.relinquish_port = ehci_relinquish_port,
|
||||
.port_handed_over = ehci_port_handed_over,
|
||||
.hub_status_data = ehci_hub_status_data,
|
||||
.hub_control = ehci_hub_control,
|
||||
.bus_suspend = ehci_bus_suspend,
|
||||
.bus_resume = ehci_bus_resume,
|
||||
.relinquish_port = ehci_relinquish_port,
|
||||
.port_handed_over = ehci_port_handed_over,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct usb_hcd *hcd = NULL;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
int ret;
|
||||
|
||||
pr_debug("In ehci_hcd_au1xxx_drv_probe\n");
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
/* FIXME we only want one one probe() not two */
|
||||
ret = usb_ehci_au1xxx_probe(&ehci_au1xxx_hc_driver, &hcd, pdev);
|
||||
#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
|
||||
/* Au1200 AB USB does not support coherent memory */
|
||||
if (!(read_c0_prid() & 0xff)) {
|
||||
printk(KERN_INFO "%s: this is chip revision AB!\n", pdev->name);
|
||||
printk(KERN_INFO "%s: update your board or re-configure"
|
||||
" the kernel\n", pdev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pdev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug("resource[1] is not IORESOURCE_IRQ");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hcd = usb_create_hcd(&ehci_au1xxx_hc_driver, &pdev->dev, "Au1xxx");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
|
||||
hcd->rsrc_start = pdev->resource[0].start;
|
||||
hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed");
|
||||
ret = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed");
|
||||
ret = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
au1xxx_start_ehc();
|
||||
|
||||
ehci = hcd_to_ehci(hcd);
|
||||
ehci->caps = hcd->regs;
|
||||
ehci->regs = hcd->regs + HC_LENGTH(readl(&ehci->caps->hc_capbase));
|
||||
/* cache this readonly data; minimize chip reads */
|
||||
ehci->hcs_params = readl(&ehci->caps->hcs_params);
|
||||
|
||||
ret = usb_add_hcd(hcd, pdev->resource[1].start,
|
||||
IRQF_DISABLED | IRQF_SHARED);
|
||||
if (ret == 0) {
|
||||
platform_set_drvdata(pdev, hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
au1xxx_stop_ehc();
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -247,35 +185,138 @@ static int ehci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
|
||||
/* FIXME we only want one one remove() not two */
|
||||
usb_ehci_au1xxx_remove(hcd, pdev);
|
||||
usb_remove_hcd(hcd);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
au1xxx_stop_ehc();
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*TBD*/
|
||||
/*static int ehci_hcd_au1xxx_drv_suspend(struct device *dev)
|
||||
#ifdef CONFIG_PM
|
||||
static int ehci_hcd_au1xxx_drv_suspend(struct platform_device *pdev,
|
||||
pm_message_t message)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
return 0;
|
||||
rc = 0;
|
||||
|
||||
if (time_before(jiffies, ehci->next_statechange))
|
||||
msleep(10);
|
||||
|
||||
/* Root hub was already suspended. Disable irq emission and
|
||||
* mark HW unaccessible, bail out if RH has been resumed. Use
|
||||
* the spinlock to properly synchronize with possible pending
|
||||
* RH suspend or resume activity.
|
||||
*
|
||||
* This is still racy as hcd->state is manipulated outside of
|
||||
* any locks =P But that will be a different fix.
|
||||
*/
|
||||
spin_lock_irqsave(&ehci->lock, flags);
|
||||
if (hcd->state != HC_STATE_SUSPENDED) {
|
||||
rc = -EINVAL;
|
||||
goto bail;
|
||||
}
|
||||
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
|
||||
(void)ehci_readl(ehci, &ehci->regs->intr_enable);
|
||||
|
||||
/* make sure snapshot being resumed re-enumerates everything */
|
||||
if (message.event == PM_EVENT_PRETHAW) {
|
||||
ehci_halt(ehci);
|
||||
ehci_reset(ehci);
|
||||
}
|
||||
|
||||
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
|
||||
|
||||
au1xxx_stop_ehc();
|
||||
|
||||
bail:
|
||||
spin_unlock_irqrestore(&ehci->lock, flags);
|
||||
|
||||
// could save FLADJ in case of Vaux power loss
|
||||
// ... we'd only use it to handle clock skew
|
||||
|
||||
return rc;
|
||||
}
|
||||
static int ehci_hcd_au1xxx_drv_resume(struct device *dev)
|
||||
|
||||
|
||||
static int ehci_hcd_au1xxx_drv_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
||||
|
||||
au1xxx_start_ehc();
|
||||
|
||||
// maybe restore FLADJ
|
||||
|
||||
if (time_before(jiffies, ehci->next_statechange))
|
||||
msleep(100);
|
||||
|
||||
/* Mark hardware accessible again as we are out of D3 state by now */
|
||||
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
|
||||
|
||||
/* If CF is still set, we maintained PCI Vaux power.
|
||||
* Just undo the effect of ehci_pci_suspend().
|
||||
*/
|
||||
if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
|
||||
int mask = INTR_MASK;
|
||||
|
||||
if (!hcd->self.root_hub->do_remote_wakeup)
|
||||
mask &= ~STS_PCD;
|
||||
ehci_writel(ehci, mask, &ehci->regs->intr_enable);
|
||||
ehci_readl(ehci, &ehci->regs->intr_enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ehci_dbg(ehci, "lost power, restarting\n");
|
||||
usb_root_hub_lost_power(hcd->self.root_hub);
|
||||
|
||||
/* Else reset, to cope with power loss or flush-to-storage
|
||||
* style "resume" having let BIOS kick in during reboot.
|
||||
*/
|
||||
(void) ehci_halt(ehci);
|
||||
(void) ehci_reset(ehci);
|
||||
|
||||
/* emptying the schedule aborts any urbs */
|
||||
spin_lock_irq(&ehci->lock);
|
||||
if (ehci->reclaim)
|
||||
end_unlink_async(ehci);
|
||||
ehci_work(ehci);
|
||||
spin_unlock_irq(&ehci->lock);
|
||||
|
||||
ehci_writel(ehci, ehci->command, &ehci->regs->command);
|
||||
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
|
||||
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
|
||||
|
||||
/* here we "know" root ports should always stay powered */
|
||||
ehci_port_power(ehci, 1);
|
||||
|
||||
hcd->state = HC_STATE_SUSPENDED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
MODULE_ALIAS("platform:au1xxx-ehci");
|
||||
|
||||
#else
|
||||
#define ehci_hcd_au1xxx_drv_suspend NULL
|
||||
#define ehci_hcd_au1xxx_drv_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver ehci_hcd_au1xxx_driver = {
|
||||
.probe = ehci_hcd_au1xxx_drv_probe,
|
||||
.remove = ehci_hcd_au1xxx_drv_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
/*.suspend = ehci_hcd_au1xxx_drv_suspend, */
|
||||
/*.resume = ehci_hcd_au1xxx_drv_resume, */
|
||||
.probe = ehci_hcd_au1xxx_drv_probe,
|
||||
.remove = ehci_hcd_au1xxx_drv_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
.suspend = ehci_hcd_au1xxx_drv_suspend,
|
||||
.resume = ehci_hcd_au1xxx_drv_resume,
|
||||
.driver = {
|
||||
.name = "au1xxx-ehci",
|
||||
.name = "au1xxx-ehci",
|
||||
.owner = THIS_MODULE,
|
||||
}
|
||||
};
|
||||
|
||||
MODULE_ALIAS("platform:au1xxx-ehci");
|
||||
|
@ -676,7 +676,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
|
||||
"%s\n"
|
||||
"SUSPENDED (no register access)\n",
|
||||
hcd->self.controller->bus->name,
|
||||
hcd->self.controller->bus_id,
|
||||
dev_name(hcd->self.controller),
|
||||
hcd->product_desc);
|
||||
goto done;
|
||||
}
|
||||
@ -688,7 +688,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
|
||||
"%s\n"
|
||||
"EHCI %x.%02x, hcd state %d\n",
|
||||
hcd->self.controller->bus->name,
|
||||
hcd->self.controller->bus_id,
|
||||
dev_name(hcd->self.controller),
|
||||
hcd->product_desc,
|
||||
i >> 8, i & 0x0ff, hcd->state);
|
||||
size -= temp;
|
||||
|
@ -56,7 +56,7 @@ int usb_hcd_fsl_probe(const struct hc_driver *driver,
|
||||
pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data;
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev,
|
||||
"No platform data for %s.\n", pdev->dev.bus_id);
|
||||
"No platform data for %s.\n", dev_name(&pdev->dev));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ int usb_hcd_fsl_probe(const struct hc_driver *driver,
|
||||
(pdata->operating_mode == FSL_USB2_DR_OTG))) {
|
||||
dev_err(&pdev->dev,
|
||||
"Non Host Mode configured for %s. Wrong driver linked.\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -77,12 +77,12 @@ int usb_hcd_fsl_probe(const struct hc_driver *driver,
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev,
|
||||
"Found HC with no IRQ. Check %s setup!\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
return -ENODEV;
|
||||
}
|
||||
irq = res->start;
|
||||
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto err1;
|
||||
@ -92,7 +92,7 @@ int usb_hcd_fsl_probe(const struct hc_driver *driver,
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev,
|
||||
"Found HC with no register addr. Check %s setup!\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
retval = -ENODEV;
|
||||
goto err2;
|
||||
}
|
||||
@ -132,7 +132,7 @@ int usb_hcd_fsl_probe(const struct hc_driver *driver,
|
||||
err2:
|
||||
usb_put_hcd(hcd);
|
||||
err1:
|
||||
dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, retval);
|
||||
dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -230,8 +230,13 @@ static void mpc83xx_usb_setup(struct usb_hcd *hcd)
|
||||
|
||||
/* put controller in host mode. */
|
||||
ehci_writel(ehci, 0x00000003, non_ehci + FSL_SOC_USB_USBMODE);
|
||||
#ifdef CONFIG_PPC_85xx
|
||||
out_be32(non_ehci + FSL_SOC_USB_PRICTRL, 0x00000008);
|
||||
out_be32(non_ehci + FSL_SOC_USB_AGECNTTHRSH, 0x00000080);
|
||||
#else
|
||||
out_be32(non_ehci + FSL_SOC_USB_PRICTRL, 0x0000000c);
|
||||
out_be32(non_ehci + FSL_SOC_USB_AGECNTTHRSH, 0x00000040);
|
||||
#endif
|
||||
out_be32(non_ehci + FSL_SOC_USB_SICTRL, 0x00000001);
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ static const char hcd_name [] = "ehci_hcd";
|
||||
#define EHCI_IAA_MSECS 10 /* arbitrary */
|
||||
#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */
|
||||
#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */
|
||||
#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */
|
||||
#define EHCI_SHRINK_FRAMES 5 /* async qh unlink delay */
|
||||
|
||||
/* Initial IRQ latency: faster than hw default */
|
||||
static int log2_irq_thresh = 0; // 0 to 6
|
||||
|
@ -77,12 +77,12 @@ static int ixp4xx_ehci_probe(struct platform_device *pdev)
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev,
|
||||
"Found HC with no IRQ. Check %s setup!\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
return -ENODEV;
|
||||
}
|
||||
irq = res->start;
|
||||
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto fail_create_hcd;
|
||||
@ -92,7 +92,7 @@ static int ixp4xx_ehci_probe(struct platform_device *pdev)
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev,
|
||||
"Found HC with no register addr. Check %s setup!\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
retval = -ENODEV;
|
||||
goto fail_request_resource;
|
||||
}
|
||||
@ -126,7 +126,7 @@ static int ixp4xx_ehci_probe(struct platform_device *pdev)
|
||||
fail_request_resource:
|
||||
usb_put_hcd(hcd);
|
||||
fail_create_hcd:
|
||||
dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, retval);
|
||||
dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,7 @@ static int __init ehci_orion_drv_probe(struct platform_device *pdev)
|
||||
if (irq <= 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"Found HC with no IRQ. Check %s setup!\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
err = -ENODEV;
|
||||
goto err1;
|
||||
}
|
||||
@ -213,7 +213,7 @@ static int __init ehci_orion_drv_probe(struct platform_device *pdev)
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev,
|
||||
"Found HC with no register addr. Check %s setup!\n",
|
||||
pdev->dev.bus_id);
|
||||
dev_name(&pdev->dev));
|
||||
err = -ENODEV;
|
||||
goto err1;
|
||||
}
|
||||
@ -233,7 +233,7 @@ static int __init ehci_orion_drv_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(&ehci_orion_hc_driver,
|
||||
&pdev->dev, pdev->dev.bus_id);
|
||||
&pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
err = -ENOMEM;
|
||||
goto err3;
|
||||
@ -276,7 +276,7 @@ static int __init ehci_orion_drv_probe(struct platform_device *pdev)
|
||||
release_mem_region(res->start, res->end - res->start + 1);
|
||||
err1:
|
||||
dev_err(&pdev->dev, "init %s fail, %d\n",
|
||||
pdev->dev.bus_id, err);
|
||||
dev_name(&pdev->dev), err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ static int ps3_ehci_probe(struct ps3_system_bus_device *dev)
|
||||
|
||||
dev->core.dma_mask = &dummy_mask; /* FIXME: for improper usb code */
|
||||
|
||||
hcd = usb_create_hcd(&ps3_ehci_hc_driver, &dev->core, dev->core.bus_id);
|
||||
hcd = usb_create_hcd(&ps3_ehci_hc_driver, &dev->core, dev_name(&dev->core));
|
||||
|
||||
if (!hcd) {
|
||||
dev_dbg(&dev->core, "%s:%d: usb_create_hcd failed\n", __func__,
|
||||
|
@ -1116,8 +1116,7 @@ static void scan_async (struct ehci_hcd *ehci)
|
||||
struct ehci_qh *qh;
|
||||
enum ehci_timer_action action = TIMER_IO_WATCHDOG;
|
||||
|
||||
if (!++(ehci->stamp))
|
||||
ehci->stamp++;
|
||||
ehci->stamp = ehci_readl(ehci, &ehci->regs->frame_index);
|
||||
timer_action_done (ehci, TIMER_ASYNC_SHRINK);
|
||||
rescan:
|
||||
qh = ehci->async->qh_next.qh;
|
||||
@ -1142,18 +1141,20 @@ static void scan_async (struct ehci_hcd *ehci)
|
||||
}
|
||||
}
|
||||
|
||||
/* unlink idle entries, reducing HC PCI usage as well
|
||||
/* unlink idle entries, reducing DMA usage as well
|
||||
* as HCD schedule-scanning costs. delay for any qh
|
||||
* we just scanned, there's a not-unusual case that it
|
||||
* doesn't stay idle for long.
|
||||
* (plus, avoids some kind of re-activation race.)
|
||||
*/
|
||||
if (list_empty (&qh->qtd_list)) {
|
||||
if (qh->stamp == ehci->stamp)
|
||||
if (list_empty(&qh->qtd_list)
|
||||
&& qh->qh_state == QH_STATE_LINKED) {
|
||||
if (!ehci->reclaim
|
||||
&& ((ehci->stamp - qh->stamp) & 0x1fff)
|
||||
>= (EHCI_SHRINK_FRAMES * 8))
|
||||
start_unlink_async(ehci, qh);
|
||||
else
|
||||
action = TIMER_ASYNC_SHRINK;
|
||||
else if (!ehci->reclaim
|
||||
&& qh->qh_state == QH_STATE_LINKED)
|
||||
start_unlink_async (ehci, qh);
|
||||
}
|
||||
|
||||
qh = qh->qh_next.qh;
|
||||
|
@ -198,7 +198,10 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
|
||||
break;
|
||||
// case TIMER_ASYNC_SHRINK:
|
||||
default:
|
||||
t = EHCI_SHRINK_JIFFIES;
|
||||
/* add a jiffie since we synch against the
|
||||
* 8 KHz uframe counter.
|
||||
*/
|
||||
t = DIV_ROUND_UP(EHCI_SHRINK_FRAMES * HZ, 1000) + 1;
|
||||
break;
|
||||
}
|
||||
mod_timer(&ehci->watchdog, t + jiffies);
|
||||
|
@ -94,6 +94,10 @@ static void write_ptddata_to_fifo(struct isp116x *isp116x, void *buf, int len)
|
||||
u16 w;
|
||||
int quot = len % 4;
|
||||
|
||||
/* buffer is already in 'usb data order', which is LE. */
|
||||
/* When reading buffer as u16, we have to take care byte order */
|
||||
/* doesn't get mixed up */
|
||||
|
||||
if ((unsigned long)dp2 & 1) {
|
||||
/* not aligned */
|
||||
for (; len > 1; len -= 2) {
|
||||
@ -105,8 +109,11 @@ static void write_ptddata_to_fifo(struct isp116x *isp116x, void *buf, int len)
|
||||
isp116x_write_data16(isp116x, (u16) * dp);
|
||||
} else {
|
||||
/* aligned */
|
||||
for (; len > 1; len -= 2)
|
||||
isp116x_raw_write_data16(isp116x, *dp2++);
|
||||
for (; len > 1; len -= 2) {
|
||||
/* Keep byte order ! */
|
||||
isp116x_raw_write_data16(isp116x, cpu_to_le16(*dp2++));
|
||||
}
|
||||
|
||||
if (len)
|
||||
isp116x_write_data16(isp116x, 0xff & *((u8 *) dp2));
|
||||
}
|
||||
@ -124,6 +131,10 @@ static void read_ptddata_from_fifo(struct isp116x *isp116x, void *buf, int len)
|
||||
u16 w;
|
||||
int quot = len % 4;
|
||||
|
||||
/* buffer is already in 'usb data order', which is LE. */
|
||||
/* When reading buffer as u16, we have to take care byte order */
|
||||
/* doesn't get mixed up */
|
||||
|
||||
if ((unsigned long)dp2 & 1) {
|
||||
/* not aligned */
|
||||
for (; len > 1; len -= 2) {
|
||||
@ -131,12 +142,16 @@ static void read_ptddata_from_fifo(struct isp116x *isp116x, void *buf, int len)
|
||||
*dp++ = w & 0xff;
|
||||
*dp++ = (w >> 8) & 0xff;
|
||||
}
|
||||
|
||||
if (len)
|
||||
*dp = 0xff & isp116x_read_data16(isp116x);
|
||||
} else {
|
||||
/* aligned */
|
||||
for (; len > 1; len -= 2)
|
||||
*dp2++ = isp116x_raw_read_data16(isp116x);
|
||||
for (; len > 1; len -= 2) {
|
||||
/* Keep byte order! */
|
||||
*dp2++ = le16_to_cpu(isp116x_raw_read_data16(isp116x));
|
||||
}
|
||||
|
||||
if (len)
|
||||
*(u8 *) dp2 = 0xff & isp116x_read_data16(isp116x);
|
||||
}
|
||||
@ -1592,7 +1607,7 @@ static int __devinit isp116x_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
/* allocate and initialize hcd */
|
||||
hcd = usb_create_hcd(&isp116x_hc_driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd(&isp116x_hc_driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
ret = -ENOMEM;
|
||||
goto err5;
|
||||
|
@ -38,6 +38,7 @@ struct isp1760_hcd {
|
||||
unsigned i_thresh;
|
||||
unsigned long reset_done;
|
||||
unsigned long next_statechange;
|
||||
unsigned int devflags;
|
||||
};
|
||||
|
||||
static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd)
|
||||
@ -378,9 +379,31 @@ static int isp1760_hc_setup(struct usb_hcd *hcd)
|
||||
{
|
||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||
int result;
|
||||
u32 scratch;
|
||||
u32 scratch, hwmode;
|
||||
|
||||
/* Setup HW Mode Control: This assumes a level active-low interrupt */
|
||||
hwmode = HW_DATA_BUS_32BIT;
|
||||
|
||||
if (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16)
|
||||
hwmode &= ~HW_DATA_BUS_32BIT;
|
||||
if (priv->devflags & ISP1760_FLAG_ANALOG_OC)
|
||||
hwmode |= HW_ANA_DIGI_OC;
|
||||
if (priv->devflags & ISP1760_FLAG_DACK_POL_HIGH)
|
||||
hwmode |= HW_DACK_POL_HIGH;
|
||||
if (priv->devflags & ISP1760_FLAG_DREQ_POL_HIGH)
|
||||
hwmode |= HW_DREQ_POL_HIGH;
|
||||
|
||||
/*
|
||||
* We have to set this first in case we're in 16-bit mode.
|
||||
* Write it twice to ensure correct upper bits if switching
|
||||
* to 16-bit mode.
|
||||
*/
|
||||
isp1760_writel(hwmode, hcd->regs + HC_HW_MODE_CTRL);
|
||||
isp1760_writel(hwmode, hcd->regs + HC_HW_MODE_CTRL);
|
||||
|
||||
isp1760_writel(0xdeadbabe, hcd->regs + HC_SCRATCH_REG);
|
||||
/* Change bus pattern */
|
||||
scratch = isp1760_readl(hcd->regs + HC_CHIP_ID_REG);
|
||||
scratch = isp1760_readl(hcd->regs + HC_SCRATCH_REG);
|
||||
if (scratch != 0xdeadbabe) {
|
||||
printk(KERN_ERR "ISP1760: Scratch test failed.\n");
|
||||
@ -403,17 +426,29 @@ static int isp1760_hc_setup(struct usb_hcd *hcd)
|
||||
|
||||
/* Step 11 passed */
|
||||
|
||||
isp1760_info(priv, "bus width: %d, oc: %s\n",
|
||||
(priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) ?
|
||||
16 : 32, (priv->devflags & ISP1760_FLAG_ANALOG_OC) ?
|
||||
"analog" : "digital");
|
||||
|
||||
/* ATL reset */
|
||||
isp1760_writel(hwmode | ALL_ATX_RESET, hcd->regs + HC_HW_MODE_CTRL);
|
||||
mdelay(10);
|
||||
isp1760_writel(hwmode, hcd->regs + HC_HW_MODE_CTRL);
|
||||
|
||||
isp1760_writel(INTERRUPT_ENABLE_MASK, hcd->regs + HC_INTERRUPT_REG);
|
||||
isp1760_writel(INTERRUPT_ENABLE_MASK, hcd->regs + HC_INTERRUPT_ENABLE);
|
||||
|
||||
/* ATL reset */
|
||||
scratch = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL);
|
||||
isp1760_writel(scratch | ALL_ATX_RESET, hcd->regs + HC_HW_MODE_CTRL);
|
||||
mdelay(10);
|
||||
isp1760_writel(scratch, hcd->regs + HC_HW_MODE_CTRL);
|
||||
|
||||
isp1760_writel(PORT1_POWER | PORT1_INIT2, hcd->regs + HC_PORT1_CTRL);
|
||||
mdelay(10);
|
||||
/*
|
||||
* PORT 1 Control register of the ISP1760 is the OTG control
|
||||
* register on ISP1761.
|
||||
*/
|
||||
if (!(priv->devflags & ISP1760_FLAG_ISP1761) &&
|
||||
!(priv->devflags & ISP1760_FLAG_PORT1_DIS)) {
|
||||
isp1760_writel(PORT1_POWER | PORT1_INIT2,
|
||||
hcd->regs + HC_PORT1_CTRL);
|
||||
mdelay(10);
|
||||
}
|
||||
|
||||
priv->hcs_params = isp1760_readl(hcd->regs + HC_HCSPARAMS);
|
||||
|
||||
@ -453,8 +488,7 @@ static int isp1760_run(struct usb_hcd *hcd)
|
||||
hcd->state = HC_STATE_RUNNING;
|
||||
isp1760_enable_interrupts(hcd);
|
||||
temp = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL);
|
||||
temp |= FINAL_HW_CONFIG;
|
||||
isp1760_writel(temp, hcd->regs + HC_HW_MODE_CTRL);
|
||||
isp1760_writel(temp | HW_GLOBAL_INTR_EN, hcd->regs + HC_HW_MODE_CTRL);
|
||||
|
||||
command = isp1760_readl(hcd->regs + HC_USBCMD);
|
||||
command &= ~(CMD_LRESET|CMD_RESET);
|
||||
@ -782,8 +816,8 @@ static void enqueue_one_int_qtd(u32 int_regs, u32 payload,
|
||||
qtd->status |= slot << 16;
|
||||
}
|
||||
|
||||
void enqueue_an_ATL_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
|
||||
struct isp1760_qtd *qtd)
|
||||
static void enqueue_an_ATL_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
|
||||
struct isp1760_qtd *qtd)
|
||||
{
|
||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||
u32 skip_map, or_map;
|
||||
@ -816,8 +850,8 @@ void enqueue_an_ATL_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
|
||||
isp1760_writel(buffstatus, hcd->regs + HC_BUFFER_STATUS_REG);
|
||||
}
|
||||
|
||||
void enqueue_an_INT_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
|
||||
struct isp1760_qtd *qtd)
|
||||
static void enqueue_an_INT_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
|
||||
struct isp1760_qtd *qtd)
|
||||
{
|
||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||
u32 skip_map, or_map;
|
||||
@ -1592,7 +1626,7 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
|
||||
struct inter_packet_info *ints;
|
||||
u32 i;
|
||||
u32 reg_base, or_reg, skip_reg;
|
||||
int flags;
|
||||
unsigned long flags;
|
||||
struct ptd ptd;
|
||||
|
||||
switch (usb_pipetype(urb->pipe)) {
|
||||
@ -2061,7 +2095,7 @@ static void isp1760_endpoint_disable(struct usb_hcd *usb_hcd,
|
||||
struct isp1760_hcd *priv = hcd_to_priv(usb_hcd);
|
||||
struct isp1760_qh *qh;
|
||||
struct isp1760_qtd *qtd;
|
||||
u32 flags;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
qh = ep->hcpriv;
|
||||
@ -2112,6 +2146,7 @@ static int isp1760_get_frame(struct usb_hcd *hcd)
|
||||
static void isp1760_stop(struct usb_hcd *hcd)
|
||||
{
|
||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||
u32 temp;
|
||||
|
||||
isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1,
|
||||
NULL, 0);
|
||||
@ -2120,7 +2155,8 @@ static void isp1760_stop(struct usb_hcd *hcd)
|
||||
spin_lock_irq(&priv->lock);
|
||||
ehci_reset(priv);
|
||||
/* Disable IRQ */
|
||||
isp1760_writel(HW_DATA_BUS_32BIT, hcd->regs + HC_HW_MODE_CTRL);
|
||||
temp = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL);
|
||||
isp1760_writel(temp &= ~HW_GLOBAL_INTR_EN, hcd->regs + HC_HW_MODE_CTRL);
|
||||
spin_unlock_irq(&priv->lock);
|
||||
|
||||
isp1760_writel(0, hcd->regs + HC_CONFIGFLAG);
|
||||
@ -2128,10 +2164,11 @@ static void isp1760_stop(struct usb_hcd *hcd)
|
||||
|
||||
static void isp1760_shutdown(struct usb_hcd *hcd)
|
||||
{
|
||||
u32 command;
|
||||
u32 command, temp;
|
||||
|
||||
isp1760_stop(hcd);
|
||||
isp1760_writel(HW_DATA_BUS_32BIT, hcd->regs + HC_HW_MODE_CTRL);
|
||||
temp = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL);
|
||||
isp1760_writel(temp &= ~HW_GLOBAL_INTR_EN, hcd->regs + HC_HW_MODE_CTRL);
|
||||
|
||||
command = isp1760_readl(hcd->regs + HC_USBCMD);
|
||||
command &= ~CMD_RUN;
|
||||
@ -2183,7 +2220,8 @@ void deinit_kmem_cache(void)
|
||||
}
|
||||
|
||||
struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq,
|
||||
u64 irqflags, struct device *dev, const char *busname)
|
||||
u64 irqflags, struct device *dev, const char *busname,
|
||||
unsigned int devflags)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
struct isp1760_hcd *priv;
|
||||
@ -2195,11 +2233,12 @@ struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq,
|
||||
/* prevent usb-core allocating DMA pages */
|
||||
dev->dma_mask = NULL;
|
||||
|
||||
hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev->bus_id);
|
||||
hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev));
|
||||
if (!hcd)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
priv = hcd_to_priv(hcd);
|
||||
priv->devflags = devflags;
|
||||
init_memory(priv);
|
||||
hcd->regs = ioremap(res_start, res_len);
|
||||
if (!hcd->regs) {
|
||||
|
@ -3,7 +3,8 @@
|
||||
|
||||
/* exports for if */
|
||||
struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq,
|
||||
u64 irqflags, struct device *dev, const char *busname);
|
||||
u64 irqflags, struct device *dev, const char *busname,
|
||||
unsigned int devflags);
|
||||
int init_kmem_once(void);
|
||||
void deinit_kmem_cache(void);
|
||||
|
||||
@ -31,6 +32,7 @@ void deinit_kmem_cache(void);
|
||||
/* Configuration Register */
|
||||
#define HC_HW_MODE_CTRL 0x300
|
||||
#define ALL_ATX_RESET (1 << 31)
|
||||
#define HW_ANA_DIGI_OC (1 << 15)
|
||||
#define HW_DATA_BUS_32BIT (1 << 8)
|
||||
#define HW_DACK_POL_HIGH (1 << 6)
|
||||
#define HW_DREQ_POL_HIGH (1 << 5)
|
||||
@ -56,13 +58,14 @@ void deinit_kmem_cache(void);
|
||||
#define PORT1_POWER (3 << 3)
|
||||
#define PORT1_INIT1 (1 << 7)
|
||||
#define PORT1_INIT2 (1 << 23)
|
||||
#define HW_OTG_CTRL_SET 0x374
|
||||
#define HW_OTG_CTRL_CLR 0x376
|
||||
|
||||
/* Interrupt Register */
|
||||
#define HC_INTERRUPT_REG 0x310
|
||||
|
||||
#define HC_INTERRUPT_ENABLE 0x314
|
||||
#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT | HC_EOT_INT)
|
||||
#define FINAL_HW_CONFIG (HW_GLOBAL_INTR_EN | HW_DATA_BUS_32BIT)
|
||||
|
||||
#define HC_ISO_INT (1 << 9)
|
||||
#define HC_ATL_INT (1 << 8)
|
||||
@ -122,6 +125,19 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh,
|
||||
#define isp1760_err(priv, fmt, args...) \
|
||||
dev_err(priv_to_hcd(priv)->self.controller, fmt, ##args)
|
||||
|
||||
/*
|
||||
* Device flags that can vary from board to board. All of these
|
||||
* indicate the most "atypical" case, so that a devflags of 0 is
|
||||
* a sane default configuration.
|
||||
*/
|
||||
#define ISP1760_FLAG_PORT1_DIS 0x00000001 /* Port 1 disabled */
|
||||
#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */
|
||||
#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */
|
||||
#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */
|
||||
#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */
|
||||
#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */
|
||||
#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */
|
||||
|
||||
/* chip memory management */
|
||||
struct memory_chunk {
|
||||
unsigned int start;
|
||||
|
@ -35,13 +35,15 @@ static int of_isp1760_probe(struct of_device *dev,
|
||||
int virq;
|
||||
u64 res_len;
|
||||
int ret;
|
||||
const unsigned int *prop;
|
||||
unsigned int devflags = 0;
|
||||
|
||||
ret = of_address_to_resource(dp, 0, &memory);
|
||||
if (ret)
|
||||
return -ENXIO;
|
||||
|
||||
res = request_mem_region(memory.start, memory.end - memory.start + 1,
|
||||
dev->dev.bus_id);
|
||||
dev_name(&dev->dev));
|
||||
if (!res)
|
||||
return -EBUSY;
|
||||
|
||||
@ -55,8 +57,32 @@ static int of_isp1760_probe(struct of_device *dev,
|
||||
virq = irq_create_of_mapping(oirq.controller, oirq.specifier,
|
||||
oirq.size);
|
||||
|
||||
if (of_device_is_compatible(dp, "nxp,usb-isp1761"))
|
||||
devflags |= ISP1760_FLAG_ISP1761;
|
||||
|
||||
if (of_get_property(dp, "port1-disable", NULL) != NULL)
|
||||
devflags |= ISP1760_FLAG_PORT1_DIS;
|
||||
|
||||
/* Some systems wire up only 16 of the 32 data lines */
|
||||
prop = of_get_property(dp, "bus-width", NULL);
|
||||
if (prop && *prop == 16)
|
||||
devflags |= ISP1760_FLAG_BUS_WIDTH_16;
|
||||
|
||||
if (of_get_property(dp, "port1-otg", NULL) != NULL)
|
||||
devflags |= ISP1760_FLAG_OTG_EN;
|
||||
|
||||
if (of_get_property(dp, "analog-oc", NULL) != NULL)
|
||||
devflags |= ISP1760_FLAG_ANALOG_OC;
|
||||
|
||||
if (of_get_property(dp, "dack-polarity", NULL) != NULL)
|
||||
devflags |= ISP1760_FLAG_DACK_POL_HIGH;
|
||||
|
||||
if (of_get_property(dp, "dreq-polarity", NULL) != NULL)
|
||||
devflags |= ISP1760_FLAG_DREQ_POL_HIGH;
|
||||
|
||||
hcd = isp1760_register(memory.start, res_len, virq,
|
||||
IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev->dev.bus_id);
|
||||
IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev_name(&dev->dev),
|
||||
devflags);
|
||||
if (IS_ERR(hcd)) {
|
||||
ret = PTR_ERR(hcd);
|
||||
goto release_reg;
|
||||
@ -87,6 +113,9 @@ static struct of_device_id of_isp1760_match[] = {
|
||||
{
|
||||
.compatible = "nxp,usb-isp1760",
|
||||
},
|
||||
{
|
||||
.compatible = "nxp,usb-isp1761",
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_isp1760_match);
|
||||
@ -116,6 +145,7 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev,
|
||||
int length;
|
||||
int status = 1;
|
||||
struct usb_hcd *hcd;
|
||||
unsigned int devflags = 0;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
@ -200,7 +230,8 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev,
|
||||
|
||||
dev->dev.dma_mask = NULL;
|
||||
hcd = isp1760_register(pci_mem_phy0, length, dev->irq,
|
||||
IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev->dev.bus_id);
|
||||
IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev_name(&dev->dev),
|
||||
devflags);
|
||||
pci_set_drvdata(dev, hcd);
|
||||
if (!hcd)
|
||||
return 0;
|
||||
|
@ -91,7 +91,7 @@ static void at91_stop_hc(struct platform_device *pdev)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *);
|
||||
static void usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *);
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
@ -184,13 +184,14 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver,
|
||||
* context, "rmmod" or something similar.
|
||||
*
|
||||
*/
|
||||
static int usb_hcd_at91_remove(struct usb_hcd *hcd,
|
||||
static void usb_hcd_at91_remove(struct usb_hcd *hcd,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
at91_stop_hc(pdev);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
if (cpu_is_at91sam9261())
|
||||
clk_put(hclk);
|
||||
@ -199,7 +200,6 @@ static int usb_hcd_at91_remove(struct usb_hcd *hcd,
|
||||
fclk = iclk = hclk = NULL;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
@ -309,7 +309,8 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
device_init_wakeup(&pdev->dev, 0);
|
||||
return usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev);
|
||||
usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
@ -34,7 +34,8 @@
|
||||
#ifdef __LITTLE_ENDIAN
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C)
|
||||
#elif __BIG_ENDIAN
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C | USBH_ENABLE_BE)
|
||||
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C | \
|
||||
USBH_ENABLE_BE)
|
||||
#else
|
||||
#error not byte order defined
|
||||
#endif
|
||||
@ -46,213 +47,87 @@
|
||||
#define USB_MCFG_RDCOMB (1<<30)
|
||||
#define USB_MCFG_SSDEN (1<<23)
|
||||
#define USB_MCFG_OHCCLKEN (1<<16)
|
||||
#ifdef CONFIG_DMA_COHERENT
|
||||
#define USB_MCFG_UCAM (1<<7)
|
||||
#else
|
||||
#define USB_MCFG_UCAM (0)
|
||||
#endif
|
||||
#define USB_MCFG_OBMEN (1<<1)
|
||||
#define USB_MCFG_OMEMEN (1<<0)
|
||||
|
||||
#define USBH_ENABLE_CE USB_MCFG_OHCCLKEN
|
||||
#ifdef CONFIG_DMA_COHERENT
|
||||
#define USBH_ENABLE_INIT (USB_MCFG_OHCCLKEN \
|
||||
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
|
||||
| USB_MCFG_SSDEN | USB_MCFG_UCAM \
|
||||
| USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
|
||||
#else
|
||||
#define USBH_ENABLE_INIT (USB_MCFG_OHCCLKEN \
|
||||
| USB_MCFG_PFEN | USB_MCFG_RDCOMB \
|
||||
| USB_MCFG_SSDEN \
|
||||
| USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
|
||||
#endif
|
||||
|
||||
#define USBH_ENABLE_INIT (USB_MCFG_PFEN | USB_MCFG_RDCOMB | \
|
||||
USBH_ENABLE_CE | USB_MCFG_SSDEN | \
|
||||
USB_MCFG_UCAM | \
|
||||
USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
|
||||
|
||||
#define USBH_DISABLE (USB_MCFG_OBMEN | USB_MCFG_OMEMEN)
|
||||
|
||||
#endif /* Au1200 */
|
||||
|
||||
extern int usb_disabled(void);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void au1xxx_start_ohc(struct platform_device *dev)
|
||||
static void au1xxx_start_ohc(void)
|
||||
{
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": starting Au1xxx OHCI USB Controller\n");
|
||||
|
||||
/* enable host controller */
|
||||
|
||||
#ifndef CONFIG_SOC_AU1200
|
||||
|
||||
au_writel(USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
au_writel(USBH_ENABLE_INIT, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
|
||||
#else /* Au1200 */
|
||||
|
||||
/* write HW defaults again in case Yamon cleared them */
|
||||
if (au_readl(USB_HOST_CONFIG) == 0) {
|
||||
au_writel(0x00d02000, USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
}
|
||||
au_writel(USBH_ENABLE_CE | au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
udelay(1000);
|
||||
au_writel(USBH_ENABLE_INIT | au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
|
||||
#endif /* Au1200 */
|
||||
|
||||
#ifndef CONFIG_SOC_AU1200
|
||||
/* wait for reset complete (read register twice; see au1500 errata) */
|
||||
while (au_readl(USB_HOST_CONFIG),
|
||||
!(au_readl(USB_HOST_CONFIG) & USBH_ENABLE_RD))
|
||||
#endif
|
||||
udelay(1000);
|
||||
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": Clock to USB host has been enabled \n");
|
||||
}
|
||||
|
||||
static void au1xxx_stop_ohc(struct platform_device *dev)
|
||||
{
|
||||
printk(KERN_DEBUG __FILE__
|
||||
": stopping Au1xxx OHCI USB Controller\n");
|
||||
|
||||
#ifndef CONFIG_SOC_AU1200
|
||||
|
||||
/* Disable clock */
|
||||
au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
|
||||
#else /* Au1200 */
|
||||
|
||||
/* Disable mem */
|
||||
au_writel(~USBH_DISABLE & au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
|
||||
au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
/* Disable clock */
|
||||
au_writel(~USBH_ENABLE_CE & au_readl(USB_HOST_CONFIG), USB_HOST_CONFIG);
|
||||
au_readl(USB_HOST_CONFIG);
|
||||
|
||||
au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(2000);
|
||||
#endif /* Au1200 */
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* configure so an HC device and id are always provided */
|
||||
/* always called with process context; sleeping is OK */
|
||||
|
||||
|
||||
/**
|
||||
* usb_ohci_au1xxx_probe - initialize Au1xxx-based HCDs
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Allocates basic resources for this USB host controller, and
|
||||
* then invokes the start() method for the HCD associated with it
|
||||
* through the hotplug entry's driver_data.
|
||||
*
|
||||
*/
|
||||
static int usb_ohci_au1xxx_probe(const struct hc_driver *driver,
|
||||
struct platform_device *dev)
|
||||
static void au1xxx_stop_ohc(void)
|
||||
{
|
||||
int retval;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
|
||||
/* Au1200 AB USB does not support coherent memory */
|
||||
if (!(read_c0_prid() & 0xff)) {
|
||||
pr_info("%s: this is chip revision AB !!\n",
|
||||
dev->name);
|
||||
pr_info("%s: update your board or re-configure the kernel\n",
|
||||
dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
#ifdef CONFIG_SOC_AU1200
|
||||
/* Disable mem */
|
||||
au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_DISABLE, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
udelay(1000);
|
||||
#endif
|
||||
|
||||
if (dev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug("resource[1] is not IORESOURCE_IRQ\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(driver, &dev->dev, "au1xxx");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
hcd->rsrc_start = dev->resource[0].start;
|
||||
hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed\n");
|
||||
retval = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed\n");
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
au1xxx_start_ohc(dev);
|
||||
ohci_hcd_init(hcd_to_ohci(hcd));
|
||||
|
||||
retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED | IRQF_SHARED);
|
||||
if (retval == 0)
|
||||
return retval;
|
||||
|
||||
au1xxx_stop_ohc(dev);
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return retval;
|
||||
/* Disable clock */
|
||||
au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_ENABLE_CE, USB_HOST_CONFIG);
|
||||
au_sync();
|
||||
}
|
||||
|
||||
|
||||
/* may be called without controller electrically present */
|
||||
/* may be called with controller, bus, and devices active */
|
||||
|
||||
/**
|
||||
* usb_hcd_au1xxx_remove - shutdown processing for Au1xxx-based HCDs
|
||||
* @dev: USB Host Controller being removed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Reverses the effect of usb_hcd_au1xxx_probe(), first invoking
|
||||
* the HCD's stop() method. It is always called from a thread
|
||||
* context, normally "rmmod", "apmd", or something similar.
|
||||
*
|
||||
*/
|
||||
static void usb_ohci_au1xxx_remove(struct usb_hcd *hcd, struct platform_device *dev)
|
||||
static int __devinit ohci_au1xxx_start(struct usb_hcd *hcd)
|
||||
{
|
||||
usb_remove_hcd(hcd);
|
||||
au1xxx_stop_ohc(dev);
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
}
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
int ret;
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
ohci_dbg(ohci, "ohci_au1xxx_start, ohci:%p", ohci);
|
||||
|
||||
static int __devinit
|
||||
ohci_au1xxx_start (struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
||||
int ret;
|
||||
|
||||
ohci_dbg (ohci, "ohci_au1xxx_start, ohci:%p", ohci);
|
||||
|
||||
if ((ret = ohci_init (ohci)) < 0)
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run (ohci)) < 0) {
|
||||
if ((ret = ohci_run(ohci)) < 0) {
|
||||
err ("can't start %s", hcd->self.bus_name);
|
||||
ohci_stop (hcd);
|
||||
ohci_stop(hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct hc_driver ohci_au1xxx_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "Au1xxx OHCI",
|
||||
@ -296,18 +171,66 @@ static const struct hc_driver ohci_au1xxx_hc_driver = {
|
||||
.start_port_reset = ohci_start_port_reset,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_hcd_au1xxx_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_debug ("In ohci_hcd_au1xxx_drv_probe");
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_ohci_au1xxx_probe(&ohci_au1xxx_hc_driver, pdev);
|
||||
#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
|
||||
/* Au1200 AB USB does not support coherent memory */
|
||||
if (!(read_c0_prid() & 0xff)) {
|
||||
printk(KERN_INFO "%s: this is chip revision AB !!\n",
|
||||
pdev->name);
|
||||
printk(KERN_INFO "%s: update your board or re-configure "
|
||||
"the kernel\n", pdev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pdev->resource[1].flags != IORESOURCE_IRQ) {
|
||||
pr_debug("resource[1] is not IORESOURCE_IRQ\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(&ohci_au1xxx_hc_driver, &pdev->dev, "au1xxx");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
|
||||
hcd->rsrc_start = pdev->resource[0].start;
|
||||
hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1;
|
||||
|
||||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
||||
pr_debug("request_mem_region failed\n");
|
||||
ret = -EBUSY;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
pr_debug("ioremap failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
au1xxx_start_ohc();
|
||||
ohci_hcd_init(hcd_to_ohci(hcd));
|
||||
|
||||
ret = usb_add_hcd(hcd, pdev->resource[1].start,
|
||||
IRQF_DISABLED | IRQF_SHARED);
|
||||
if (ret == 0) {
|
||||
platform_set_drvdata(pdev, hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
au1xxx_stop_ohc();
|
||||
iounmap(hcd->regs);
|
||||
err2:
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
err1:
|
||||
usb_put_hcd(hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -315,30 +238,78 @@ static int ohci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
|
||||
usb_ohci_au1xxx_remove(hcd, pdev);
|
||||
return 0;
|
||||
}
|
||||
/*TBD*/
|
||||
/*static int ohci_hcd_au1xxx_drv_suspend(struct platform_device *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
usb_remove_hcd(hcd);
|
||||
au1xxx_stop_ohc();
|
||||
iounmap(hcd->regs);
|
||||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
||||
usb_put_hcd(hcd);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int ohci_hcd_au1xxx_drv_resume(struct platform_device *dev)
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ohci_hcd_au1xxx_drv_suspend(struct platform_device *pdev,
|
||||
pm_message_t message)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
rc = 0;
|
||||
|
||||
/* Root hub was already suspended. Disable irq emission and
|
||||
* mark HW unaccessible, bail out if RH has been resumed. Use
|
||||
* the spinlock to properly synchronize with possible pending
|
||||
* RH suspend or resume activity.
|
||||
*
|
||||
* This is still racy as hcd->state is manipulated outside of
|
||||
* any locks =P But that will be a different fix.
|
||||
*/
|
||||
spin_lock_irqsave(&ohci->lock, flags);
|
||||
if (hcd->state != HC_STATE_SUSPENDED) {
|
||||
rc = -EINVAL;
|
||||
goto bail;
|
||||
}
|
||||
ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
|
||||
(void)ohci_readl(ohci, &ohci->regs->intrdisable);
|
||||
|
||||
/* make sure snapshot being resumed re-enumerates everything */
|
||||
if (message.event == PM_EVENT_PRETHAW)
|
||||
ohci_usb_reset(ohci);
|
||||
|
||||
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
|
||||
|
||||
au1xxx_stop_ohc();
|
||||
bail:
|
||||
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ohci_hcd_au1xxx_drv_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
|
||||
au1xxx_start_ohc();
|
||||
|
||||
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
|
||||
ohci_finish_controller_resume(hcd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
#else
|
||||
#define ohci_hcd_au1xxx_drv_suspend NULL
|
||||
#define ohci_hcd_au1xxx_drv_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver ohci_hcd_au1xxx_driver = {
|
||||
.probe = ohci_hcd_au1xxx_drv_probe,
|
||||
.remove = ohci_hcd_au1xxx_drv_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
/*.suspend = ohci_hcd_au1xxx_drv_suspend, */
|
||||
/*.resume = ohci_hcd_au1xxx_drv_resume, */
|
||||
.suspend = ohci_hcd_au1xxx_drv_suspend,
|
||||
.resume = ohci_hcd_au1xxx_drv_resume,
|
||||
.driver = {
|
||||
.name = "au1xxx-ohci",
|
||||
.owner = THIS_MODULE,
|
||||
|
@ -651,7 +651,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
|
||||
"%s\n"
|
||||
"%s version " DRIVER_VERSION "\n",
|
||||
hcd->self.controller->bus->name,
|
||||
hcd->self.controller->bus_id,
|
||||
dev_name(hcd->self.controller),
|
||||
hcd->product_desc,
|
||||
hcd_name);
|
||||
|
||||
|
@ -974,7 +974,7 @@ MODULE_LICENSE ("GPL");
|
||||
#define PCI_DRIVER ohci_pci_driver
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SA1111
|
||||
#if defined(CONFIG_ARCH_SA1100) && defined(CONFIG_SA1111)
|
||||
#include "ohci-sa1111.c"
|
||||
#define SA1111_DRIVER ohci_hcd_sa1111_driver
|
||||
#endif
|
||||
|
@ -329,7 +329,7 @@ static int usb_hcd_omap_probe (const struct hc_driver *driver,
|
||||
}
|
||||
|
||||
|
||||
hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd (driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto err0;
|
||||
|
@ -109,8 +109,6 @@ static struct clk *usb_clk;
|
||||
|
||||
static int isp1301_probe(struct i2c_adapter *adap);
|
||||
static int isp1301_detach(struct i2c_client *client);
|
||||
static int isp1301_command(struct i2c_client *client, unsigned int cmd,
|
||||
void *arg);
|
||||
|
||||
static const unsigned short normal_i2c[] =
|
||||
{ ISP1301_I2C_ADDR, ISP1301_I2C_ADDR + 1, I2C_CLIENT_END };
|
||||
@ -123,30 +121,37 @@ static struct i2c_client_address_data addr_data = {
|
||||
};
|
||||
|
||||
struct i2c_driver isp1301_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "isp1301_pnx",
|
||||
},
|
||||
.attach_adapter = isp1301_probe,
|
||||
.detach_client = isp1301_detach,
|
||||
.command = isp1301_command
|
||||
};
|
||||
|
||||
static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind)
|
||||
{
|
||||
struct i2c_client *c;
|
||||
int err;
|
||||
|
||||
c = kzalloc(sizeof(*c), GFP_KERNEL);
|
||||
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
strcpy(c->name, "isp1301");
|
||||
strlcpy(c->name, "isp1301_pnx", I2C_NAME_SIZE);
|
||||
c->flags = 0;
|
||||
c->addr = addr;
|
||||
c->adapter = adap;
|
||||
c->driver = &isp1301_driver;
|
||||
|
||||
err = i2c_attach_client(c);
|
||||
if (err) {
|
||||
kfree(c);
|
||||
return err;
|
||||
}
|
||||
|
||||
isp1301_i2c_client = c;
|
||||
|
||||
return i2c_attach_client(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isp1301_probe(struct i2c_adapter *adap)
|
||||
@ -161,13 +166,6 @@ static int isp1301_detach(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No commands defined */
|
||||
static int isp1301_command(struct i2c_client *client, unsigned int cmd,
|
||||
void *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void i2c_write(u8 buf, u8 subaddr)
|
||||
{
|
||||
char tmpbuf[2];
|
||||
@ -389,7 +387,7 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev)
|
||||
while ((__raw_readl(USB_OTG_CLK_STAT) & USB_CLOCK_MASK) !=
|
||||
USB_CLOCK_MASK) ;
|
||||
|
||||
hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd (driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
err("Failed to allocate HC buffer");
|
||||
ret = -ENOMEM;
|
||||
|
@ -14,8 +14,8 @@
|
||||
*/
|
||||
|
||||
#include <linux/signal.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <asm/of_platform.h>
|
||||
#include <asm/prom.h>
|
||||
|
||||
|
||||
|
@ -129,7 +129,7 @@ static int ps3_ohci_probe(struct ps3_system_bus_device *dev)
|
||||
|
||||
dev->core.dma_mask = &dummy_mask; /* FIXME: for improper usb code */
|
||||
|
||||
hcd = usb_create_hcd(&ps3_ohci_hc_driver, &dev->core, dev->core.bus_id);
|
||||
hcd = usb_create_hcd(&ps3_ohci_hc_driver, &dev->core, dev_name(&dev->core));
|
||||
|
||||
if (!hcd) {
|
||||
dev_dbg(&dev->core, "%s:%d: usb_create_hcd failed\n", __func__,
|
||||
|
@ -159,9 +159,6 @@ static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed)
|
||||
{
|
||||
int branch;
|
||||
|
||||
if (ohci_to_hcd(ohci)->state == HC_STATE_QUIESCING)
|
||||
return -EAGAIN;
|
||||
|
||||
ed->state = ED_OPER;
|
||||
ed->ed_prev = NULL;
|
||||
ed->ed_next = NULL;
|
||||
|
@ -143,7 +143,7 @@ static int ohci_hcd_sm501_drv_probe(struct platform_device *pdev)
|
||||
goto err2;
|
||||
}
|
||||
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto err2;
|
||||
|
@ -113,7 +113,7 @@ static int ssb_ohci_attach(struct ssb_device *dev)
|
||||
ssb_device_enable(dev, flags);
|
||||
|
||||
hcd = usb_create_hcd(&ssb_ohci_hc_driver, dev->dev,
|
||||
dev->dev->bus_id);
|
||||
dev_name(dev->dev));
|
||||
if (!hcd)
|
||||
goto err_dev_disable;
|
||||
ohcidev = hcd_to_ssb_ohci(hcd);
|
||||
|
@ -312,9 +312,9 @@ static void put_child_connect_map(struct r8a66597 *r8a66597, int address)
|
||||
static void set_pipe_reg_addr(struct r8a66597_pipe *pipe, u8 dma_ch)
|
||||
{
|
||||
u16 pipenum = pipe->info.pipenum;
|
||||
unsigned long fifoaddr[] = {D0FIFO, D1FIFO, CFIFO};
|
||||
unsigned long fifosel[] = {D0FIFOSEL, D1FIFOSEL, CFIFOSEL};
|
||||
unsigned long fifoctr[] = {D0FIFOCTR, D1FIFOCTR, CFIFOCTR};
|
||||
const unsigned long fifoaddr[] = {D0FIFO, D1FIFO, CFIFO};
|
||||
const unsigned long fifosel[] = {D0FIFOSEL, D1FIFOSEL, CFIFOSEL};
|
||||
const unsigned long fifoctr[] = {D0FIFOCTR, D1FIFOCTR, CFIFOCTR};
|
||||
|
||||
if (dma_ch > R8A66597_PIPE_NO_DMA) /* dma fifo not use? */
|
||||
dma_ch = R8A66597_PIPE_NO_DMA;
|
||||
@ -863,6 +863,32 @@ static void disable_r8a66597_pipe_all(struct r8a66597 *r8a66597,
|
||||
dev->dma_map = 0;
|
||||
}
|
||||
|
||||
static u16 get_interval(struct urb *urb, __u8 interval)
|
||||
{
|
||||
u16 time = 1;
|
||||
int i;
|
||||
|
||||
if (urb->dev->speed == USB_SPEED_HIGH) {
|
||||
if (interval > IITV)
|
||||
time = IITV;
|
||||
else
|
||||
time = interval ? interval - 1 : 0;
|
||||
} else {
|
||||
if (interval > 128) {
|
||||
time = IITV;
|
||||
} else {
|
||||
/* calculate the nearest value for PIPEPERI */
|
||||
for (i = 0; i < 7; i++) {
|
||||
if ((1 << i) < interval &&
|
||||
(1 << (i + 1) > interval))
|
||||
time = 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
static unsigned long get_timer_interval(struct urb *urb, __u8 interval)
|
||||
{
|
||||
__u8 i;
|
||||
@ -901,10 +927,7 @@ static void init_pipe_info(struct r8a66597 *r8a66597, struct urb *urb,
|
||||
info.interval = 0;
|
||||
info.timer_interval = 0;
|
||||
} else {
|
||||
if (ep->bInterval > IITV)
|
||||
info.interval = IITV;
|
||||
else
|
||||
info.interval = ep->bInterval ? ep->bInterval - 1 : 0;
|
||||
info.interval = get_interval(urb, ep->bInterval);
|
||||
info.timer_interval = get_timer_interval(urb, ep->bInterval);
|
||||
}
|
||||
if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
|
||||
@ -2244,6 +2267,7 @@ static int __init r8a66597_probe(struct platform_device *pdev)
|
||||
struct r8a66597 *r8a66597;
|
||||
int ret = 0;
|
||||
int i;
|
||||
unsigned long irq_trigger;
|
||||
|
||||
if (pdev->dev.dma_mask) {
|
||||
ret = -EINVAL;
|
||||
@ -2302,7 +2326,11 @@ static int __init r8a66597_probe(struct platform_device *pdev)
|
||||
INIT_LIST_HEAD(&r8a66597->child_device);
|
||||
|
||||
hcd->rsrc_start = res->start;
|
||||
ret = usb_add_hcd(hcd, irq, IRQF_DISABLED);
|
||||
if (irq_sense == INTL)
|
||||
irq_trigger = IRQF_TRIGGER_LOW;
|
||||
else
|
||||
irq_trigger = IRQF_TRIGGER_FALLING;
|
||||
ret = usb_add_hcd(hcd, irq, IRQF_DISABLED | irq_trigger);
|
||||
if (ret != 0) {
|
||||
err("Failed to add hcd");
|
||||
goto clean_up;
|
||||
|
@ -1674,7 +1674,7 @@ sl811h_probe(struct platform_device *dev)
|
||||
}
|
||||
|
||||
/* allocate and initialize hcd */
|
||||
hcd = usb_create_hcd(&sl811h_hc_driver, &dev->dev, dev->dev.bus_id);
|
||||
hcd = usb_create_hcd(&sl811h_hc_driver, &dev->dev, dev_name(&dev->dev));
|
||||
if (!hcd) {
|
||||
retval = -ENOMEM;
|
||||
goto err5;
|
||||
|
@ -3124,7 +3124,7 @@ static int __devinit u132_probe(struct platform_device *pdev)
|
||||
if (pdev->dev.dma_mask)
|
||||
return -EINVAL;
|
||||
|
||||
hcd = usb_create_hcd(&u132_hc_driver, &pdev->dev, pdev->dev.bus_id);
|
||||
hcd = usb_create_hcd(&u132_hc_driver, &pdev->dev, dev_name(&pdev->dev));
|
||||
if (!hcd) {
|
||||
printk(KERN_ERR "failed to create the usb hcd struct for U132\n"
|
||||
);
|
||||
|
@ -12,7 +12,7 @@
|
||||
* (C) Copyright 2004 Alan Stern, stern@rowland.harvard.edu
|
||||
*/
|
||||
|
||||
static __u8 root_hub_hub_des[] =
|
||||
static const __u8 root_hub_hub_des[] =
|
||||
{
|
||||
0x09, /* __u8 bLength; */
|
||||
0x29, /* __u8 bDescriptorType; Hub-descriptor */
|
||||
|
@ -1421,7 +1421,8 @@ ofail: mutex_unlock(&cp->mutex);
|
||||
|
||||
|
||||
/* IOCTL functions */
|
||||
static int auerchar_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
|
||||
static long auerchar_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
pauerchar_t ccp = (pauerchar_t) file->private_data;
|
||||
int ret = 0;
|
||||
@ -1452,7 +1453,7 @@ static int auerchar_ioctl (struct inode *inode, struct file *file, unsigned int
|
||||
mutex_unlock(&ccp->mutex);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lock_kernel();
|
||||
switch (cmd) {
|
||||
|
||||
/* return != 0 if Transmitt channel ready to send */
|
||||
@ -1547,9 +1548,10 @@ static int auerchar_ioctl (struct inode *inode, struct file *file, unsigned int
|
||||
|
||||
default:
|
||||
dbg ("IOCTL_AU_UNKNOWN");
|
||||
ret = -ENOIOCTLCMD;
|
||||
ret = -ENOTTY;
|
||||
break;
|
||||
}
|
||||
unlock_kernel();
|
||||
/* release the mutexes */
|
||||
mutex_unlock(&cp->mutex);
|
||||
mutex_unlock(&ccp->mutex);
|
||||
@ -1860,7 +1862,7 @@ static const struct file_operations auerswald_fops =
|
||||
.llseek = no_llseek,
|
||||
.read = auerchar_read,
|
||||
.write = auerchar_write,
|
||||
.ioctl = auerchar_ioctl,
|
||||
.unlocked_ioctl = auerchar_ioctl,
|
||||
.open = auerchar_open,
|
||||
.release = auerchar_release,
|
||||
};
|
||||
|
@ -6,8 +6,6 @@
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, as published by
|
||||
* the Free Software Foundation, version 2.
|
||||
*
|
||||
* $Id: emi62.c,v 1.15 2002/04/23 06:13:59 tapio Exp $
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
|
@ -656,29 +656,6 @@ static int ftdi_elan_release(struct inode *inode, struct file *file)
|
||||
}
|
||||
|
||||
|
||||
#define FTDI_ELAN_IOC_MAGIC 0xA1
|
||||
#define FTDI_ELAN_IOCDEBUG _IOC(_IOC_WRITE, FTDI_ELAN_IOC_MAGIC, 1, 132)
|
||||
static int ftdi_elan_ioctl(struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case FTDI_ELAN_IOCDEBUG:{
|
||||
char line[132];
|
||||
int size = strncpy_from_user(line,
|
||||
(const char __user *)arg, sizeof(line));
|
||||
if (size < 0) {
|
||||
return -EINVAL;
|
||||
} else {
|
||||
printk(KERN_ERR "TODO: ioctl %s\n", line);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* blocking bulk reads are used to get data from the device
|
||||
@ -1222,7 +1199,6 @@ static ssize_t ftdi_elan_write(struct file *file,
|
||||
static const struct file_operations ftdi_elan_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.ioctl = ftdi_elan_ioctl,
|
||||
.read = ftdi_elan_read,
|
||||
.write = ftdi_elan_write,
|
||||
.open = ftdi_elan_open,
|
||||
|
@ -474,8 +474,8 @@ static ssize_t iowarrior_write(struct file *file,
|
||||
/**
|
||||
* iowarrior_ioctl
|
||||
*/
|
||||
static int iowarrior_ioctl(struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
static long iowarrior_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct iowarrior *dev = NULL;
|
||||
__u8 *buffer;
|
||||
@ -493,6 +493,7 @@ static int iowarrior_ioctl(struct inode *inode, struct file *file,
|
||||
return -ENOMEM;
|
||||
|
||||
/* lock this object */
|
||||
lock_kernel();
|
||||
mutex_lock(&dev->mutex);
|
||||
|
||||
/* verify that the device wasn't unplugged */
|
||||
@ -584,6 +585,7 @@ static int iowarrior_ioctl(struct inode *inode, struct file *file,
|
||||
error_out:
|
||||
/* unlock the device */
|
||||
mutex_unlock(&dev->mutex);
|
||||
unlock_kernel();
|
||||
kfree(buffer);
|
||||
return retval;
|
||||
}
|
||||
@ -719,7 +721,7 @@ static const struct file_operations iowarrior_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = iowarrior_write,
|
||||
.read = iowarrior_read,
|
||||
.ioctl = iowarrior_ioctl,
|
||||
.unlocked_ioctl = iowarrior_ioctl,
|
||||
.open = iowarrior_open,
|
||||
.release = iowarrior_release,
|
||||
.poll = iowarrior_poll,
|
||||
|
@ -104,9 +104,7 @@ static int close_rio(struct inode *inode, struct file *file)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ioctl_rio(struct inode *inode, struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
static long ioctl_rio(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct RioCommand rio_cmd;
|
||||
struct rio_usb_data *rio = &rio_instance;
|
||||
@ -116,6 +114,7 @@ ioctl_rio(struct inode *inode, struct file *file, unsigned int cmd,
|
||||
int retries;
|
||||
int retval=0;
|
||||
|
||||
lock_kernel();
|
||||
mutex_lock(&(rio->lock));
|
||||
/* Sanity check to make sure rio is connected, powered, etc */
|
||||
if (rio->present == 0 || rio->rio_dev == NULL) {
|
||||
@ -254,6 +253,7 @@ ioctl_rio(struct inode *inode, struct file *file, unsigned int cmd,
|
||||
|
||||
err_out:
|
||||
mutex_unlock(&(rio->lock));
|
||||
unlock_kernel();
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -433,7 +433,7 @@ file_operations usb_rio_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = read_rio,
|
||||
.write = write_rio,
|
||||
.ioctl = ioctl_rio,
|
||||
.unlocked_ioctl = ioctl_rio,
|
||||
.open = open_rio,
|
||||
.release = close_rio,
|
||||
};
|
||||
|
@ -2982,9 +2982,8 @@ sisusb_handle_command(struct sisusb_usb_data *sisusb, struct sisusb_command *y,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
sisusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
static long
|
||||
sisusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct sisusb_usb_data *sisusb;
|
||||
struct sisusb_info x;
|
||||
@ -2995,6 +2994,7 @@ sisusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
|
||||
if (!(sisusb = (struct sisusb_usb_data *)file->private_data))
|
||||
return -ENODEV;
|
||||
|
||||
lock_kernel();
|
||||
mutex_lock(&sisusb->lock);
|
||||
|
||||
/* Sanity check */
|
||||
@ -3053,6 +3053,7 @@ sisusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
|
||||
|
||||
err_out:
|
||||
mutex_unlock(&sisusb->lock);
|
||||
unlock_kernel();
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -3066,9 +3067,7 @@ sisusb_compat_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
||||
case SISUSB_GET_CONFIG_SIZE:
|
||||
case SISUSB_GET_CONFIG:
|
||||
case SISUSB_COMMAND:
|
||||
lock_kernel();
|
||||
retval = sisusb_ioctl(f->f_path.dentry->d_inode, f, cmd, arg);
|
||||
unlock_kernel();
|
||||
retval = sisusb_ioctl(f, cmd, arg);
|
||||
return retval;
|
||||
|
||||
default:
|
||||
@ -3087,7 +3086,7 @@ static const struct file_operations usb_sisusb_fops = {
|
||||
#ifdef SISUSB_NEW_CONFIG_COMPAT
|
||||
.compat_ioctl = sisusb_compat_ioctl,
|
||||
#endif
|
||||
.ioctl = sisusb_ioctl
|
||||
.unlocked_ioctl = sisusb_ioctl
|
||||
};
|
||||
|
||||
static struct usb_class_driver usb_sisusb_class = {
|
||||
|
@ -146,7 +146,7 @@ static ssize_t lcd_read(struct file *file, char __user * buffer, size_t count, l
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int lcd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
|
||||
static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct usb_lcd *dev;
|
||||
u16 bcdDevice;
|
||||
@ -158,12 +158,14 @@ static int lcd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, u
|
||||
|
||||
switch (cmd) {
|
||||
case IOCTL_GET_HARD_VERSION:
|
||||
lock_kernel();
|
||||
bcdDevice = le16_to_cpu((dev->udev)->descriptor.bcdDevice);
|
||||
sprintf(buf,"%1d%1d.%1d%1d",
|
||||
(bcdDevice & 0xF000)>>12,
|
||||
(bcdDevice & 0xF00)>>8,
|
||||
(bcdDevice & 0xF0)>>4,
|
||||
(bcdDevice & 0xF));
|
||||
unlock_kernel();
|
||||
if (copy_to_user((void __user *)arg,buf,strlen(buf))!=0)
|
||||
return -EFAULT;
|
||||
break;
|
||||
@ -272,7 +274,7 @@ static const struct file_operations lcd_fops = {
|
||||
.read = lcd_read,
|
||||
.write = lcd_write,
|
||||
.open = lcd_open,
|
||||
.ioctl = lcd_ioctl,
|
||||
.unlocked_ioctl = lcd_ioctl,
|
||||
.release = lcd_release,
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include "usb_mon.h"
|
||||
@ -42,19 +43,8 @@ static ssize_t mon_stat_read(struct file *file, char __user *buf,
|
||||
size_t nbytes, loff_t *ppos)
|
||||
{
|
||||
struct snap *sp = file->private_data;
|
||||
loff_t pos = *ppos;
|
||||
int cnt;
|
||||
|
||||
if (pos < 0 || pos >= sp->slen)
|
||||
return 0;
|
||||
if (nbytes == 0)
|
||||
return 0;
|
||||
if ((cnt = sp->slen - pos) > nbytes)
|
||||
cnt = nbytes;
|
||||
if (copy_to_user(buf, sp->str + pos, cnt))
|
||||
return -EFAULT;
|
||||
*ppos = pos + cnt;
|
||||
return cnt;
|
||||
return simple_read_from_buffer(buf, nbytes, ppos, sp->str, sp->slen);
|
||||
}
|
||||
|
||||
static int mon_stat_release(struct inode *inode, struct file *file)
|
||||
|
@ -64,14 +64,6 @@ config USB_SERIAL_AIRCABLE
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called aircable.
|
||||
|
||||
config USB_SERIAL_AIRPRIME
|
||||
tristate "USB AirPrime CDMA Wireless Driver"
|
||||
help
|
||||
Say Y here if you want to use a AirPrime CDMA Wireless PC card.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called airprime.
|
||||
|
||||
config USB_SERIAL_ARK3116
|
||||
tristate "USB ARK Micro 3116 USB Serial Driver"
|
||||
help
|
||||
|
@ -12,7 +12,6 @@ usbserial-obj-$(CONFIG_USB_EZUSB) += ezusb.o
|
||||
usbserial-objs := usb-serial.o generic.o bus.o $(usbserial-obj-y)
|
||||
|
||||
obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o
|
||||
obj-$(CONFIG_USB_SERIAL_AIRPRIME) += airprime.o
|
||||
obj-$(CONFIG_USB_SERIAL_ARK3116) += ark3116.o
|
||||
obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o
|
||||
obj-$(CONFIG_USB_SERIAL_CH341) += ch341.o
|
||||
|
@ -1,353 +0,0 @@
|
||||
/*
|
||||
* AirPrime CDMA Wireless Serial USB driver
|
||||
*
|
||||
* Copyright (C) 2005-2006 Greg Kroah-Hartman <gregkh@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/serial.h>
|
||||
|
||||
static struct usb_device_id id_table [] = {
|
||||
{ USB_DEVICE(0x0c88, 0x17da) }, /* Kyocera Wireless KPC650/Passport */
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, id_table);
|
||||
|
||||
#define URB_TRANSFER_BUFFER_SIZE 4096
|
||||
#define NUM_READ_URBS 4
|
||||
#define NUM_WRITE_URBS 4
|
||||
#define NUM_BULK_EPS 3
|
||||
#define MAX_BULK_EPS 6
|
||||
|
||||
/* if overridden by the user, then use their value for the size of the
|
||||
* read and write urbs, and the number of endpoints */
|
||||
static int buffer_size = URB_TRANSFER_BUFFER_SIZE;
|
||||
static int endpoints = NUM_BULK_EPS;
|
||||
static int debug;
|
||||
struct airprime_private {
|
||||
spinlock_t lock;
|
||||
int outstanding_urbs;
|
||||
int throttled;
|
||||
struct urb *read_urbp[NUM_READ_URBS];
|
||||
|
||||
/* Settings for the port */
|
||||
int rts_state; /* Handshaking pins (outputs) */
|
||||
int dtr_state;
|
||||
int cts_state; /* Handshaking pins (inputs) */
|
||||
int dsr_state;
|
||||
int dcd_state;
|
||||
int ri_state;
|
||||
};
|
||||
|
||||
static int airprime_send_setup(struct usb_serial_port *port)
|
||||
{
|
||||
struct usb_serial *serial = port->serial;
|
||||
struct airprime_private *priv;
|
||||
|
||||
dbg("%s", __func__);
|
||||
|
||||
if (port->number != 0)
|
||||
return 0;
|
||||
|
||||
priv = usb_get_serial_port_data(port);
|
||||
|
||||
if (port->tty) {
|
||||
int val = 0;
|
||||
if (priv->dtr_state)
|
||||
val |= 0x01;
|
||||
if (priv->rts_state)
|
||||
val |= 0x02;
|
||||
|
||||
return usb_control_msg(serial->dev,
|
||||
usb_rcvctrlpipe(serial->dev, 0),
|
||||
0x22, 0x21, val, 0, NULL, 0,
|
||||
USB_CTRL_SET_TIMEOUT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void airprime_read_bulk_callback(struct urb *urb)
|
||||
{
|
||||
struct usb_serial_port *port = urb->context;
|
||||
unsigned char *data = urb->transfer_buffer;
|
||||
struct tty_struct *tty;
|
||||
int result;
|
||||
int status = urb->status;
|
||||
|
||||
dbg("%s - port %d", __func__, port->number);
|
||||
|
||||
if (status) {
|
||||
dbg("%s - nonzero read bulk status received: %d",
|
||||
__func__, status);
|
||||
return;
|
||||
}
|
||||
usb_serial_debug_data(debug, &port->dev, __func__,
|
||||
urb->actual_length, data);
|
||||
|
||||
tty = port->tty;
|
||||
if (tty && urb->actual_length) {
|
||||
tty_insert_flip_string(tty, data, urb->actual_length);
|
||||
tty_flip_buffer_push(tty);
|
||||
}
|
||||
|
||||
result = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (result)
|
||||
dev_err(&port->dev,
|
||||
"%s - failed resubmitting read urb, error %d\n",
|
||||
__func__, result);
|
||||
return;
|
||||
}
|
||||
|
||||
static void airprime_write_bulk_callback(struct urb *urb)
|
||||
{
|
||||
struct usb_serial_port *port = urb->context;
|
||||
struct airprime_private *priv = usb_get_serial_port_data(port);
|
||||
int status = urb->status;
|
||||
unsigned long flags;
|
||||
|
||||
dbg("%s - port %d", __func__, port->number);
|
||||
|
||||
/* free up the transfer buffer, as usb_free_urb() does not do this */
|
||||
kfree(urb->transfer_buffer);
|
||||
|
||||
if (status)
|
||||
dbg("%s - nonzero write bulk status received: %d",
|
||||
__func__, status);
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
--priv->outstanding_urbs;
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
usb_serial_port_softint(port);
|
||||
}
|
||||
|
||||
static int airprime_open(struct usb_serial_port *port, struct file *filp)
|
||||
{
|
||||
struct airprime_private *priv = usb_get_serial_port_data(port);
|
||||
struct usb_serial *serial = port->serial;
|
||||
struct urb *urb;
|
||||
char *buffer = NULL;
|
||||
int i;
|
||||
int result = 0;
|
||||
|
||||
dbg("%s - port %d", __func__, port->number);
|
||||
|
||||
/* initialize our private data structure if it isn't already created */
|
||||
if (!priv) {
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
result = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
spin_lock_init(&priv->lock);
|
||||
usb_set_serial_port_data(port, priv);
|
||||
}
|
||||
|
||||
/* Set some sane defaults */
|
||||
priv->rts_state = 1;
|
||||
priv->dtr_state = 1;
|
||||
|
||||
for (i = 0; i < NUM_READ_URBS; ++i) {
|
||||
buffer = kmalloc(buffer_size, GFP_KERNEL);
|
||||
if (!buffer) {
|
||||
dev_err(&port->dev, "%s - out of memory.\n",
|
||||
__func__);
|
||||
result = -ENOMEM;
|
||||
goto errout;
|
||||
}
|
||||
urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!urb) {
|
||||
kfree(buffer);
|
||||
dev_err(&port->dev, "%s - no more urbs?\n",
|
||||
__func__);
|
||||
result = -ENOMEM;
|
||||
goto errout;
|
||||
}
|
||||
usb_fill_bulk_urb(urb, serial->dev,
|
||||
usb_rcvbulkpipe(serial->dev,
|
||||
port->bulk_out_endpointAddress),
|
||||
buffer, buffer_size,
|
||||
airprime_read_bulk_callback, port);
|
||||
result = usb_submit_urb(urb, GFP_KERNEL);
|
||||
if (result) {
|
||||
usb_free_urb(urb);
|
||||
kfree(buffer);
|
||||
dev_err(&port->dev,
|
||||
"%s - failed submitting read urb %d for port %d, error %d\n",
|
||||
__func__, i, port->number, result);
|
||||
goto errout;
|
||||
}
|
||||
/* remember this urb so we can kill it when the
|
||||
port is closed */
|
||||
priv->read_urbp[i] = urb;
|
||||
}
|
||||
|
||||
airprime_send_setup(port);
|
||||
|
||||
goto out;
|
||||
|
||||
errout:
|
||||
/* some error happened, cancel any submitted urbs and clean up
|
||||
anything that got allocated successfully */
|
||||
|
||||
while (i-- != 0) {
|
||||
urb = priv->read_urbp[i];
|
||||
buffer = urb->transfer_buffer;
|
||||
usb_kill_urb(urb);
|
||||
usb_free_urb(urb);
|
||||
kfree(buffer);
|
||||
}
|
||||
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
static void airprime_close(struct usb_serial_port *port, struct file *filp)
|
||||
{
|
||||
struct airprime_private *priv = usb_get_serial_port_data(port);
|
||||
int i;
|
||||
|
||||
dbg("%s - port %d", __func__, port->number);
|
||||
|
||||
priv->rts_state = 0;
|
||||
priv->dtr_state = 0;
|
||||
|
||||
mutex_lock(&port->serial->disc_mutex);
|
||||
if (!port->serial->disconnected)
|
||||
airprime_send_setup(port);
|
||||
mutex_unlock(&port->serial->disc_mutex);
|
||||
|
||||
for (i = 0; i < NUM_READ_URBS; ++i) {
|
||||
usb_kill_urb(priv->read_urbp[i]);
|
||||
kfree(priv->read_urbp[i]->transfer_buffer);
|
||||
usb_free_urb(priv->read_urbp[i]);
|
||||
}
|
||||
|
||||
/* free up private structure */
|
||||
kfree(priv);
|
||||
usb_set_serial_port_data(port, NULL);
|
||||
}
|
||||
|
||||
static int airprime_write(struct usb_serial_port *port,
|
||||
const unsigned char *buf, int count)
|
||||
{
|
||||
struct airprime_private *priv = usb_get_serial_port_data(port);
|
||||
struct usb_serial *serial = port->serial;
|
||||
struct urb *urb;
|
||||
unsigned char *buffer;
|
||||
unsigned long flags;
|
||||
int status;
|
||||
dbg("%s - port %d", __func__, port->number);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (priv->outstanding_urbs > NUM_WRITE_URBS) {
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
dbg("%s - write limit hit\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
buffer = kmalloc(count, GFP_ATOMIC);
|
||||
if (!buffer) {
|
||||
dev_err(&port->dev, "out of memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
urb = usb_alloc_urb(0, GFP_ATOMIC);
|
||||
if (!urb) {
|
||||
dev_err(&port->dev, "no more free urbs\n");
|
||||
kfree(buffer);
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(buffer, buf, count);
|
||||
|
||||
usb_serial_debug_data(debug, &port->dev, __func__, count, buffer);
|
||||
|
||||
usb_fill_bulk_urb(urb, serial->dev,
|
||||
usb_sndbulkpipe(serial->dev,
|
||||
port->bulk_out_endpointAddress),
|
||||
buffer, count,
|
||||
airprime_write_bulk_callback, port);
|
||||
|
||||
/* send it down the pipe */
|
||||
status = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (status) {
|
||||
dev_err(&port->dev,
|
||||
"%s - usb_submit_urb(write bulk) failed with status = %d\n",
|
||||
__func__, status);
|
||||
count = status;
|
||||
kfree(buffer);
|
||||
} else {
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
++priv->outstanding_urbs;
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
}
|
||||
/* we are done with this urb, so let the host driver
|
||||
* really free it when it is finished with it */
|
||||
usb_free_urb(urb);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct usb_driver airprime_driver = {
|
||||
.name = "airprime",
|
||||
.probe = usb_serial_probe,
|
||||
.disconnect = usb_serial_disconnect,
|
||||
.id_table = id_table,
|
||||
.no_dynamic_id = 1,
|
||||
};
|
||||
|
||||
static struct usb_serial_driver airprime_device = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "airprime",
|
||||
},
|
||||
.usb_driver = &airprime_driver,
|
||||
.id_table = id_table,
|
||||
.open = airprime_open,
|
||||
.close = airprime_close,
|
||||
.write = airprime_write,
|
||||
};
|
||||
|
||||
static int __init airprime_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
airprime_device.num_ports = endpoints;
|
||||
if (endpoints < 0 || endpoints >= MAX_BULK_EPS)
|
||||
airprime_device.num_ports = NUM_BULK_EPS;
|
||||
|
||||
retval = usb_serial_register(&airprime_device);
|
||||
if (retval)
|
||||
return retval;
|
||||
retval = usb_register(&airprime_driver);
|
||||
if (retval)
|
||||
usb_serial_deregister(&airprime_device);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void __exit airprime_exit(void)
|
||||
{
|
||||
dbg("%s", __func__);
|
||||
|
||||
usb_deregister(&airprime_driver);
|
||||
usb_serial_deregister(&airprime_device);
|
||||
}
|
||||
|
||||
module_init(airprime_init);
|
||||
module_exit(airprime_exit);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_param(debug, bool, S_IRUGO | S_IWUSR);
|
||||
MODULE_PARM_DESC(debug, "Debug enabled");
|
||||
module_param(buffer_size, int, 0);
|
||||
MODULE_PARM_DESC(buffer_size,
|
||||
"Size of the transfer buffers in bytes (default 4096)");
|
||||
module_param(endpoints, int, 0);
|
||||
MODULE_PARM_DESC(endpoints, "Number of bulk EPs to configure (default 3)");
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user