www.pudn.com > isp1161.rar > hc_simple.c
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*
* simple generic USB HCD frontend Version 0.9.5 (10/28/2001)
* for embedded HCs (eg. isp1161, MPC850, ...)
*
* USB URB handling, hci_ hcs_
* URB queueing, qu_
* Transfer scheduling, sh_
*
* Roman Weissgaerber weissg@vienna.at (C) 2001
*
*-------------------------------------------------------------------------*
* 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
*
*-------------------------------------------------------------------------*/
/*
* V0.9.5 (10/28/2001) start_int()/stop_int() enable interrupt processing on demand
* ISOC processing
* V0.9.2 (10/10/2001) sh_done_list: on error just reject last packet
* V0.9.1 (10/8/2001) unlink_urb check also for USB_ST_URB_PENDING
* V0.9 (9/23/2001)
* * * * * * * */
/* main lock for urb access */
static spinlock_t usb_urb_lock = SPIN_LOCK_UNLOCKED;
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* URB HCD API function layer
* * * */
/*-------------------------------------------------------------------------*/
/* set actual length to 0 and set status
* queue URB
* */
static inline int hcs_urb_queue (hci_t * hci, urb_t * urb)
{
int i;
if (usb_pipeisoc (urb->pipe)) {
for (i = 0; i < urb->number_of_packets; i++) {
urb->iso_frame_desc[i].actual_length = 0;
urb->iso_frame_desc[i].status = -EXDEV;
}
/* urb->next hack : 1 .. resub, 0 .. single shot */
urb->interval = urb->next ? 1 : 0;
}
urb->status = USB_ST_URB_PENDING;
urb->actual_length = 0;
urb->error_count = 0;
if (usb_pipecontrol (urb->pipe))
hc_flush_data_cache (hci, urb->setup_packet, 8);
if (usb_pipeout (urb->pipe))
hc_flush_data_cache (hci, urb->transfer_buffer,
urb->transfer_buffer_length);
qu_queue_urb (hci, urb);
return 0;
}
/*-------------------------------------------------------------------------*/
/* return path (complete - callback) of URB API
* also handles resubmition of intr URBs
* */
static int hcs_return_urb (hci_t * hci, urb_t * urb, int resub_ok)
{
struct usb_device * dev = urb->dev;
int resubmit = 0;
if (urb_debug)
urb_print (urb, "RET", usb_pipeout (urb->pipe));
resubmit = urb->interval && resub_ok;
urb->dev = urb->hcpriv = NULL;
if (urb->complete)
urb->complete (urb); /* call complete */
if (resubmit) { /* requeue the URB */
urb->dev = dev;
hcs_urb_queue (hci, urb);
}
return 0;
}
/*-------------------------------------------------------------------------*/
/* got a transfer request
* */
static int hci_submit_urb (urb_t * urb)
{
hci_t * hci;
unsigned int pipe = urb->pipe;
unsigned long flags;
int ret;
if (!urb->dev || !urb->dev->bus || urb->hcpriv)
return -EINVAL;
if (usb_endpoint_halted (urb->dev,
usb_pipeendpoint (pipe), usb_pipeout (pipe)))
return -EPIPE;
hci = (hci_t *) urb->dev->bus->hcpriv;
/* a request to the virtual root hub */
if (usb_pipedevice (pipe) == hci->rh.devnum) {
if (urb_debug > 1)
urb_print (urb, "SUB-RH", usb_pipein (pipe));
//printk("EEpig submit urb to virtual root devnum %d\n",hci->rh.devnum);
return rh_submit_urb (urb);
}
if (urb_debug)
urb_print (urb, "SUB", usb_pipein (pipe));
/* queue the URB to its endpoint-queue */
spin_lock_irqsave (&usb_urb_lock, flags);
ret = hcs_urb_queue (hci, urb);
spin_unlock_irqrestore (&usb_urb_lock, flags);
return ret;
}
/*-------------------------------------------------------------------------*/
/* unlink URB: mark the URB to unlink
* */
static int hci_unlink_urb (urb_t * urb)
{
unsigned long flags;
hci_t * hci;
DECLARE_WAITQUEUE (wait, current);
void * comp = NULL;
if (!urb) /* just to be sure */
return -EINVAL;
if (!urb->dev || !urb->dev->bus)
return -ENODEV;
hci = (hci_t *) urb->dev->bus->hcpriv;
/* a request to the virtual root hub */
if (usb_pipedevice (urb->pipe) == hci->rh.devnum) {
return rh_unlink_urb (urb);
}
if (urb_debug)
urb_print (urb, "UNLINK", 1);
spin_lock_irqsave (&usb_urb_lock, flags);
if (!list_empty (&urb->urb_list) && urb->status == USB_ST_URB_PENDING) { /* URB active? */
if (urb->transfer_flags & (USB_ASYNC_UNLINK | USB_TIMEOUT_KILLED)) {
/* asynchron with callback */
list_del (&urb->urb_list); /* relink the urb to the del list */
list_add (&urb->urb_list, &hci->del_list);
spin_unlock_irqrestore (&usb_urb_lock, flags);
} else { /* synchron without callback */
add_wait_queue (&hci->waitq, &wait);
set_current_state (TASK_UNINTERRUPTIBLE);
comp = urb->complete;
urb->complete = NULL;
list_del (&urb->urb_list); /* relink the urb to the del list */
list_add (&urb->urb_list, &hci->del_list);
spin_unlock_irqrestore (&usb_urb_lock, flags);
schedule_timeout(HZ/50);
if (!list_empty (&urb->urb_list))
list_del (&urb->urb_list);
urb->complete = comp;
urb->hcpriv = NULL;
remove_wait_queue (&hci->waitq, &wait);
}
} else { /* hcd does not own URB but we keep the driver happy anyway */
spin_unlock_irqrestore (&usb_urb_lock, flags);
if (urb->complete && (urb->transfer_flags & USB_ASYNC_UNLINK)) {
urb->status = -ENOENT;
urb->actual_length = 0;
urb->complete (urb);
urb->status = 0;
} else {
urb->status = -ENOENT;
}
}
return 0;
}
/*-------------------------------------------------------------------------*/
/* allocate private data space for a usb device
* */
static int hci_alloc_dev (struct usb_device * usb_dev)
{
struct hci_device * dev;
int i;
dev = kmalloc (sizeof (*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
memset (dev, 0, sizeof (*dev));
for (i = 0; i < 32; i++) {
INIT_LIST_HEAD (&(dev->ed [i].urb_queue));
dev->ed [i].pipe_head = NULL;
}
usb_dev->hcpriv = dev;
if (hc_verbose)
printk ("USB HC dev alloc %d bytes\n", sizeof (*dev));
return 0;
}
/*-------------------------------------------------------------------------*/
/* free private data space of usb device
* */
static int hci_free_dev (struct usb_device * usb_dev)
{
if (hc_verbose)
printk ("USB HC dev free\n");
if (usb_dev->hcpriv)
kfree (usb_dev->hcpriv);
usb_dev->hcpriv = NULL;
return 0;
}
/*-------------------------------------------------------------------------*/
/* tell us the current USB frame number
* */
static int hci_get_current_frame_number (struct usb_device *usb_dev)
{
hci_t * hci = usb_dev->bus->hcpriv;
return GET_FRAME_NUMBER (hci);
}
/*-------------------------------------------------------------------------*/
/* make a list of all io-functions
* */
static struct usb_operations hci_device_operations = {
hci_alloc_dev,
hci_free_dev,
hci_get_current_frame_number,
hci_submit_urb,
hci_unlink_urb
};
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* URB queueing:
*
* For each type of transfer (INTR, BULK, ISO, CTRL) there is a list of
* active URBs.
* (hci->intr_list, hci->bulk_list, hci->iso_list, hci->ctrl_list)
* For every endpoint the head URB of the queued URBs is linked to one of
* those lists.
*
* The rest of the queued URBs of an endpoint are linked into a
* private URB list for each endpoint. (hci_dev->ed [endpoint_io].urb_queue)
* hci_dev->ed [endpoint_io].pipe_head .. points to the head URB which is
* in one of the active URB lists.
*
* The index of an endpoint consists of its number and its direction.
*
* The state of an intr and iso URB is 0.
* For ctrl URBs the states are US_CTRL_SETUP, US_CTRL_DATA, US_CTRL_ACK
* Bulk URBs states are US_BULK and US_BULK0 (with 0-len packet)
*
* * * */
#ifdef HC_URB_TIMEOUT
static void qu_urb_timeout (unsigned long lurb)
{
urb_t * urb = (urb_t *) lurb;
urb->transfer_flags |= USB_TIMEOUT_KILLED;
hci_unlink_urb (urb);
}
#endif
/*-------------------------------------------------------------------------*/
static inline int qu_pipeindex (__u32 pipe)
{
return (usb_pipeendpoint (pipe) << 1) | (usb_pipecontrol (pipe) ?
0 : usb_pipeout (pipe));
}
static inline void qu_seturbstate (urb_t * urb, int state)
{
urb->pipe &= ~0x1f;
urb->pipe |= state & 0x1f;
}
static inline int qu_urbstate (urb_t * urb)
{
return urb->pipe & 0x1f;
}
/*-------------------------------------------------------------------------*/
static inline void qu_queue_active_urb (hci_t * hci, urb_t * urb, epd_t * ed)
{
int urb_state = 0;
switch (usb_pipetype (urb->pipe)) {
case PIPE_CONTROL:
list_add (&urb->urb_list, &hci->ctrl_list);
urb_state = US_CTRL_SETUP;
break;
case PIPE_BULK:
list_add (&urb->urb_list, &hci->bulk_list);
if ((urb->transfer_flags & USB_ZERO_PACKET) &&
urb->transfer_buffer_length > 0 &&
((urb->transfer_buffer_length %
usb_maxpacket (urb->dev, urb->pipe,
usb_pipeout (urb->pipe))) == 0)) {
urb_state = US_BULK0;
}
break;
case PIPE_INTERRUPT:
urb->start_frame = hci->frame_number;
list_add (&urb->urb_list, &hci->intr_list);
break;
case PIPE_ISOCHRONOUS:
list_add (&urb->urb_list, &hci->iso_list);
break;
}
#ifdef HC_URB_TIMEOUT
if (urb->timeout) {
ed->timeout.data = (unsigned long) urb;
ed->timeout.expires = urb->timeout + jiffies;
ed->timeout.function = qu_urb_timeout;
add_timer (&ed->timeout);
}
#endif
qu_seturbstate (urb, urb_state);
}
static int qu_queue_urb (hci_t * hci, urb_t * urb)
{
struct hci_device * hci_dev = usb_to_hci (urb->dev);
epd_t * ed = &hci_dev->ed [qu_pipeindex (urb->pipe)];
/* for ISOC transfers calculate start frame index */
if (usb_pipeisoc (urb->pipe) && urb->transfer_flags & USB_ISO_ASAP) {
urb->start_frame = ((ed->pipe_head)? (ed->last_iso + 1):
hci_get_current_frame_number (urb->dev) + 1) & 0xffff;
}
if (ed->pipe_head) {
__list_add (&urb->urb_list, ed->urb_queue.prev, &(ed->urb_queue));
} else {
ed->pipe_head = urb;
qu_queue_active_urb (hci, urb, ed);
if (++hci->active_urbs == 1)
hc_start_int (hci);
}
return 0;
}
/*-------------------------------------------------------------------------*/
/* return path (after transfer)
* remove URB from queue and return it to the api layer
* */
static urb_t * qu_next_urb (hci_t * hci, urb_t * urb, int resub_ok)
{
struct hci_device * hci_dev = usb_to_hci (urb->dev);
epd_t * ed = &hci_dev->ed [qu_pipeindex (urb->pipe)];
list_del (&urb->urb_list);
INIT_LIST_HEAD (&urb->urb_list);
if (ed->pipe_head == urb) {
#ifdef HC_URB_TIMEOUT
if (urb->timeout)
del_timer (&ed->timeout);
#endif
if (!--hci->active_urbs)
hc_stop_int (hci);
if (!list_empty (&ed->urb_queue)) {
urb = list_entry (ed->urb_queue.next, urb_t, urb_list);
list_del (&urb->urb_list);
INIT_LIST_HEAD (&urb->urb_list);
ed->pipe_head = urb;
qu_queue_active_urb (hci, urb, ed);
} else {
ed->pipe_head = NULL;
urb = NULL;
}
}
return urb;
}
static urb_t * qu_return_urb (hci_t * hci, urb_t * urb, int resub_ok)
{
urb_t * next_urb;
next_urb = qu_next_urb (hci, urb, resub_ok);
hcs_return_urb (hci, urb, resub_ok);
return next_urb;
}
/*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* SCHEDULE operations
*
* * * */
static int sh_scan_iso_urb_list (hci_t * hci, struct list_head * list_lh, int frame_number)
{
struct list_head * lh = list_lh->next;
urb_t * urb;
hci->td_array->len = 0;
while (lh != list_lh) {
urb = list_entry (lh, urb_t, urb_list);
lh = lh->next;
if (((frame_number - urb->start_frame) & 0x7ff) < urb->number_of_packets) {
if (!sh_add_packet (hci, urb))
return 0;
else {
if (((frame_number - urb->start_frame) & 0x7ff) > 0x400) {
if (qu_urbstate(urb) > 0)
urb = qu_return_urb (hci, urb, 1);
else
urb = qu_next_urb (hci, urb, 1);
if (lh == list_lh && urb)
lh = &urb->urb_list;
}
}
}
}
return 1;
}
static int sh_scan_urb_list (hci_t * hci, struct list_head * list_lh)
{
struct list_head * lh;
urb_t * urb;
list_for_each (lh, list_lh) {
urb = list_entry (lh, urb_t, urb_list);
if (!usb_pipeint (urb->pipe) ||
(((hci->frame_number - urb->start_frame) & 0x7ff) >= urb->interval)) {
if (!sh_add_packet (hci, urb))
return 0;
else {
// printk("INT: start: %d fr_no: %d int: %d pint: %d\n", urb->start_frame, hci->frame_number, urb->interval, usb_pipeint (urb->pipe));
urb->start_frame = hci->frame_number;
}
}
}
return 1;
}
/*-------------------------------------------------------------------------*/
/* scan lists of active URBs and construct a schedule for a frame
* */
static int sh_schedule_trans (hci_t * hci)
{
int units_left;
struct list_head * lh;
hci->td_array->len = 0;
units_left = sh_scan_urb_list (hci, &hci->intr_list);
if (units_left) { /* add CTRL transfers */
units_left = sh_scan_urb_list (hci, &hci->ctrl_list);
}
if (units_left) { /* add BULK transfers */
sh_scan_urb_list (hci, &hci->bulk_list);
}
/* be fair to each BULK URB (move list head around) */
if (!list_empty (&hci->bulk_list)) {
lh = hci->bulk_list.next;
list_del (&hci->bulk_list);
list_add (&hci->bulk_list, lh);
}
return 0;
}
/*-------------------------------------------------------------------------*/
/* add some parts of the active URB to the schedule
* */
static int sh_add_packet (hci_t * hci, urb_t * urb)
{
__u8 * data = NULL;
int len = 0;
int toggle = 0;
int maxps = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
int endpoint = usb_pipeendpoint (urb->pipe);
int address = usb_pipedevice (urb->pipe);
int slow = usb_pipeslow (urb->pipe);
int out = usb_pipeout (urb->pipe);
int pid = 0;
int ret;
int i = 0;
int iso = 0;
if (maxps == 0) maxps = 8;
/* calculate len, toggle bit and add the transaction */
switch (usb_pipetype (urb->pipe)) {
case PIPE_ISOCHRONOUS:
pid = out ? PID_OUT : PID_IN;
iso = 1;
i = hci->frame_number - urb->start_frame;
data = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
len = urb->iso_frame_desc[i].length;
break;
case PIPE_BULK: /* BULK and BULK0 */
case PIPE_INTERRUPT:
pid = out ? PID_OUT : PID_IN;
len = urb->transfer_buffer_length - urb->actual_length;
data = urb->transfer_buffer + urb->actual_length;
toggle = usb_gettoggle (urb->dev, endpoint, out);
break;
case PIPE_CONTROL:
switch (qu_urbstate (urb)) {
case US_CTRL_SETUP:
len = 8;
pid = PID_SETUP;
data = urb->setup_packet;
toggle = 0;
break;
case US_CTRL_DATA:
if (urb->transfer_buffer_length != 0) {
pid = out ? PID_OUT : PID_IN;
len = urb->transfer_buffer_length - urb->actual_length;
data = urb->transfer_buffer + urb->actual_length;
toggle = (urb->actual_length & maxps) ? 0 : 1;
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe), toggle);
break;
} else
/* correct state and fall through */
qu_seturbstate (urb, US_CTRL_ACK);
case US_CTRL_ACK:
len = 0;
/* reply in opposite direction */
pid = !out ? PID_OUT : PID_IN;
toggle = 1;
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe), toggle);
break;
}
}
ret = hc_add_trans (hci, len, data, toggle, maxps, slow,
endpoint, address, pid, iso);
#ifdef DEBUG1
printk("transfer_pa: addr:%d ep:%d pid:%x tog:%x iso:%x sl:%x max:%d\n len:%d ret:%d data:%p left:%d\n",
address, endpoint, pid, toggle, iso, slow, maxps, len, ret, data, hci->hp.units_left);
#endif
if (ret >= 0) {
hci->td_array->td [hci->td_array->len].urb = urb;
hci->td_array->td [hci->td_array->len].len = ret;
hci->td_array->td [hci->td_array->len].iso_index = i;
hci->td_array->len ++;
return 1;
}
return 0;
}
/*-------------------------------------------------------------------------*/
/* parse the done_list
* */
static int sh_done_list (hci_t * hci)
{
int actbytes;
int active = 0;
void * data = NULL;
// void * data_urb = NULL;
int cc;
int maxps;
int toggle;
urb_t * urb;
int urb_state;
int ret = 1; /* -1 parse abbort, 1 parse ok, 0 last element */
int trans = 0;
int len;
int iso_index = 0;
for (trans = 0; ret && trans < hci->td_array->len && trans < MAX_TRANS; trans++) {
urb = hci->td_array->td [trans].urb;
len = hci->td_array->td [trans].len;
if (usb_pipeisoc (urb->pipe)) {
iso_index = hci->td_array->td [trans].iso_index;
data = urb->transfer_buffer +
urb->iso_frame_desc [iso_index].offset;
toggle = 0;
} else {
data = urb->transfer_buffer + urb->actual_length;
toggle = usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe));
}
urb_state = qu_urbstate (urb);
ret = hc_parse_trans (hci, &actbytes, data, &cc, &toggle, len);
maxps = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
if (maxps == 0) maxps = 8;
#ifdef DEBUG1
printk("hc USB %d error %d in frame %d ep %d out %d addr %d actbytes %d:%p tog %d\n",
urb->error_count, cc, hci->frame_number,
usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe),
usb_pipedevice (urb->pipe),
actbytes, data, toggle);
#endif
if (cc != TD_NOTACCESSED) {
active = (urb_state != US_CTRL_SETUP) &&
(actbytes && !(actbytes & (maxps - 1)) &&
(urb->transfer_buffer_length != urb->actual_length + actbytes));
#ifdef DEBUG1
printk("hc USBx active %d, cc %x, urb_state %x, ret %x\n", active, cc, urb_state, ret);
#endif
if (!(urb->transfer_flags & USB_DISABLE_SPD) && cc == TD_DATAUNDERRUN)
cc = 0;
if (cc) { /* last packet has an error */
if (hc_error_verbose)
printk("hc USB %d error %d in frame %d ep %d out %d addr %d au: %d\n",
urb->error_count, cc, hci->frame_number,
usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe), usb_pipedevice (urb->pipe), hci->active_urbs);
if (++urb->error_count > 3 || cc == TD_CC_STALL) {
active = 0;
urb_state = 0; /* return */
} else {
active = 1;
}
// actbytes = actbytes & ~(maxps - 1);
} else
urb->error_count = 0;
if (urb_state != US_CTRL_SETUP) { /* no error */
urb->actual_length += actbytes;
usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe),
usb_pipeout (urb->pipe),
toggle);
}
if (usb_pipeisoc (urb->pipe)) {
urb->iso_frame_desc [iso_index].actual_length = actbytes;
urb->iso_frame_desc [iso_index].status = cc_to_error [cc];
active = (iso_index < urb->number_of_packets);
}
if (!active) {
if (!(urb_state--)) {
urb->status = cc_to_error [cc];
qu_return_urb (hci, urb, 1);
} else {
qu_seturbstate (urb, urb_state);
}
}
} else
urb->error_count = 0;
}
hci->td_array->len = 0;
return ret;
}
/*-------------------------------------------------------------------------*/
/* scan the delete list (qu_return_urb removes URB from list)
* */
static void sh_del_list (hci_t * hci)
{
struct list_head * lh = &hci->del_list;
urb_t * urb;
if (!list_empty (lh)) {
do {
lh = lh->next;
urb = list_entry (lh, urb_t, urb_list);
qu_return_urb (hci, urb, 0);
} while (!list_empty (lh));
wake_up (&hci->waitq);
}
return;
}