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;
}