// SPDX-License-Identifier: GPL-2.0+ /* * c67x00-sched.c: Cypress C67X00 USB Host Controller Driver - TD scheduling * * Copyright (C) 2006-2008 Barco N.V. * Derived from the Cypress cy7c67200/300 ezusb linux driver and * based on multiple host controller drivers inside the linux kernel.
*/
/* * These are the stages for a control urb, they are kept * in both urb->interval and td->privdata.
*/ #define SETUP_STAGE 0 #define DATA_STAGE 1 #define STATUS_STAGE 2
/* These are needed for handling the toggle bits: * an urb can be dequeued while a td is in progress * after checking the td, the toggle bit might need to
* be fixed */ struct c67x00_ep_data *ep_data; unsignedint pipe;
};
struct c67x00_urb_priv { struct list_head hep_node; struct urb *urb; int port; int cnt; /* packet number for isoc */ int status; struct c67x00_ep_data *ep_data;
};
/* * frame_add * Software wraparound for framenumbers.
*/ staticinline u16 frame_add(u16 a, u16 b)
{ return (a + b) & HOST_FRAME_MASK;
}
/* * frame_after - is frame a after frame b
*/ staticinlineint frame_after(u16 a, u16 b)
{ return ((HOST_FRAME_MASK + a - b) & HOST_FRAME_MASK) <
(HOST_FRAME_MASK / 2);
}
/* * frame_after_eq - is frame a after or equal to frame b
*/ staticinlineint frame_after_eq(u16 a, u16 b)
{ return ((HOST_FRAME_MASK + 1 + a - b) & HOST_FRAME_MASK) <
(HOST_FRAME_MASK / 2);
}
/* * c67x00_release_urb - remove link from all tds to this urb * Disconnects the urb from it's tds, so that it can be given back. * pre: urb->hcpriv != NULL
*/ staticvoid c67x00_release_urb(struct c67x00_hcd *c67x00, struct urb *urb)
{ struct c67x00_td *td; struct c67x00_urb_priv *urbp;
BUG_ON(!urb);
c67x00->urb_count--;
if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
c67x00->urb_iso_count--; if (c67x00->urb_iso_count == 0)
c67x00->max_frame_bw = MAX_FRAME_BW_STD;
}
/* TODO this might be not so efficient when we've got many urbs! * Alternatives: * * only clear when needed * * keep a list of tds with each urbp
*/
list_for_each_entry(td, &c67x00->td_list, td_list) if (urb == td->urb)
td->urb = NULL;
/* hold a reference to udev as long as this endpoint lives,
* this is needed to possibly fix the data toggle */
ep_data->dev = usb_get_dev(urb->dev);
hep->hcpriv = ep_data;
/* For ISOC and INT endpoints, start ASAP: */
ep_data->next_frame = frame_add(c67x00->current_frame, 1);
/* Add the endpoint data to one of the pipe lists; must be added
in order of endpoint address */
type = usb_pipetype(urb->pipe); if (list_empty(&ep_data->node)) {
list_add(&ep_data->node, &c67x00->list[type]);
} else { struct c67x00_ep_data *prev;
if (!list_empty(&ep->urb_list))
dev_warn(c67x00_hcd_dev(c67x00), "error: urb list not empty\n");
spin_lock_irqsave(&c67x00->lock, flags);
/* loop waiting for all transfers in the endpoint queue to complete */ while (c67x00_ep_data_free(ep)) { /* Drop the lock so we can sleep waiting for the hardware */
spin_unlock_irqrestore(&c67x00->lock, flags);
/* it could happen that we reinitialize this completion, while * somebody was waiting for that completion. The timeout and
* while loop handle such cases, but this might be improved */
reinit_completion(&c67x00->endpoint_disable);
c67x00_sched_kick(c67x00);
wait_for_completion_timeout(&c67x00->endpoint_disable, 1 * HZ);
switch (usb_pipetype(urb->pipe)) { case PIPE_CONTROL:
urb->interval = SETUP_STAGE; break; case PIPE_INTERRUPT: break; case PIPE_BULK: break; case PIPE_ISOCHRONOUS: if (c67x00->urb_iso_count == 0)
c67x00->max_frame_bw = MAX_FRAME_BW_ISO;
c67x00->urb_iso_count++; /* Assume always URB_ISO_ASAP, FIXME */ if (list_empty(&urbp->ep_data->queue))
urb->start_frame = urbp->ep_data->next_frame; else { /* Go right after the last one */ struct urb *last_urb;
/* * td_addr and buf_addr must be word aligned
*/ staticint c67x00_create_td(struct c67x00_hcd *c67x00, struct urb *urb, void *data, int len, int pid, int toggle, unsignedlong privdata)
{ struct c67x00_td *td; struct c67x00_urb_priv *urbp = urb->hcpriv; const __u8 active_flag = 1, retry_cnt = 3;
__u8 cmd = 0; int tt = 0;
if (c67x00_claim_frame_bw(c67x00, urb, len, usb_pipeisoc(urb->pipe)
|| usb_pipeint(urb->pipe))) return -EMSGSIZE; /* Not really an error, but expected */
td = kzalloc(sizeof(*td), GFP_ATOMIC); if (!td) return -ENOMEM;
staticint c67x00_add_data_urb(struct c67x00_hcd *c67x00, struct urb *urb)
{ int remaining; int toggle; int pid; int ret = 0; int maxps; int need_empty;
toggle ^= 1;
remaining -= len; if (usb_pipecontrol(urb->pipe)) break;
}
return 0;
}
/* * return 0 in case more bandwidth is available, else errorcode
*/ staticint c67x00_add_ctrl_urb(struct c67x00_hcd *c67x00, struct urb *urb)
{ int ret; int pid;
switch (urb->interval) { default: case SETUP_STAGE:
ret = c67x00_create_td(c67x00, urb, urb->setup_packet,
8, USB_PID_SETUP, 0, SETUP_STAGE); if (ret) return ret;
urb->interval = SETUP_STAGE;
usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
usb_pipeout(urb->pipe), 1); break; case DATA_STAGE: if (urb->transfer_buffer_length) {
ret = c67x00_add_data_urb(c67x00, urb); if (ret) return ret; break;
}
fallthrough; case STATUS_STAGE:
pid = !usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN;
ret = c67x00_create_td(c67x00, urb, NULL, 0, pid, 1,
STATUS_STAGE); if (ret) return ret; break;
}
return 0;
}
/* * return 0 in case more bandwidth is available, else errorcode
*/ staticint c67x00_add_int_urb(struct c67x00_hcd *c67x00, struct urb *urb)
{ struct c67x00_urb_priv *urbp = urb->hcpriv;
staticvoid c67x00_fill_from_list(struct c67x00_hcd *c67x00, int type, int (*add)(struct c67x00_hcd *, struct urb *))
{ struct c67x00_ep_data *ep_data; struct urb *urb;
/* traverse every endpoint on the list */
list_for_each_entry(ep_data, &c67x00->list[type], node) { if (!list_empty(&ep_data->queue)) { /* and add the first urb */ /* isochronous transfer rely on this */
urb = list_entry(ep_data->queue.next, struct c67x00_urb_priv,
hep_node)->urb;
add(c67x00, urb);
}
}
}
/* Check if we can proceed */ if (!list_empty(&c67x00->td_list)) {
dev_warn(c67x00_hcd_dev(c67x00), "TD list not empty! This should not happen!\n");
list_for_each_entry_safe(td, ttd, &c67x00->td_list, td_list) {
dbg_td(c67x00, td, "Unprocessed td");
c67x00_release_td(td);
}
}
/* Remove all td's from the list which come * after last_td and are meant for the same pipe.
* This is used when a short packet has occurred */ staticinlinevoid c67x00_clear_pipe(struct c67x00_hcd *c67x00, struct c67x00_td *last_td)
{ struct c67x00_td *td, *tmp;
td = last_td;
tmp = last_td; while (td->td_list.next != &c67x00->td_list) {
td = list_entry(td->td_list.next, struct c67x00_td, td_list); if (td->pipe == last_td->pipe) {
c67x00_release_td(td);
td = tmp;
}
tmp = td;
}
}
/* * c67x00_check_td_list - handle tds which have been processed by the c67x00 * pre: current_td == 0
*/ staticinlinevoid c67x00_check_td_list(struct c67x00_hcd *c67x00)
{ struct c67x00_td *td, *tmp; struct urb *urb; int ack_ok; int clear_endpoint;
list_for_each_entry_safe(td, tmp, &c67x00->td_list, td_list) { /* get the TD */
c67x00_parse_td(c67x00, td);
urb = td->urb; /* urb can be NULL! */
ack_ok = 0;
clear_endpoint = 1;
/* When an error occurs, all td's for that pipe go into an * inactive state. This state matches successful transfers so
* we must make sure not to service them. */ if (td->status & TD_ERROR_MASK) {
c67x00_giveback_urb(c67x00, urb,
c67x00_td_to_error(c67x00, td)); goto cont;
}
if ((td->status & TD_STATUSMASK_NAK) || !td_sequence_ok(td) ||
!td_acked(td)) goto cont;
/* Sequence ok and acked, don't need to fix toggle */
ack_ok = 1;
if (unlikely(td->status & TD_STATUSMASK_OVF)) { if (td_residue(td) & TD_RESIDUE_OVERFLOW) { /* Overflow */
c67x00_giveback_urb(c67x00, urb, -EOVERFLOW); goto cont;
}
}
cont: if (clear_endpoint)
c67x00_clear_pipe(c67x00, td); if (ack_ok)
usb_settoggle(td_udev(td), usb_pipeendpoint(td->pipe),
usb_pipeout(td->pipe),
!(td->ctrl_reg & SEQ_SEL)); /* next in list could have been removed, due to clear_pipe! */
tmp = list_entry(td->td_list.next, typeof(*td), td_list);
c67x00_release_td(td);
}
}
staticinlineint c67x00_all_tds_processed(struct c67x00_hcd *c67x00)
{ /* If all tds are processed, we can check the previous frame (if * there was any) and start our next frame.
*/ return !c67x00_ll_husb_get_current_td(c67x00->sie);
}
/* * Send td to C67X00
*/ staticvoid c67x00_send_td(struct c67x00_hcd *c67x00, struct c67x00_td *td)
{ int len = td_length(td);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.