Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
asl.c
Go to the documentation of this file.
1 /*
2  * Wireless Host Controller (WHC) asynchronous schedule management.
3  *
4  * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License version
8  * 2 as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 #include <linux/kernel.h>
19 #include <linux/gfp.h>
20 #include <linux/dma-mapping.h>
21 #include <linux/uwb/umc.h>
22 #include <linux/usb.h>
23 
24 #include "../../wusbcore/wusbhc.h"
25 
26 #include "whcd.h"
27 
28 static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
29  struct whc_qset **next, struct whc_qset **prev)
30 {
31  struct list_head *n, *p;
32 
33  BUG_ON(list_empty(&whc->async_list));
34 
35  n = qset->list_node.next;
36  if (n == &whc->async_list)
37  n = n->next;
38  p = qset->list_node.prev;
39  if (p == &whc->async_list)
40  p = p->prev;
41 
42  *next = container_of(n, struct whc_qset, list_node);
43  *prev = container_of(p, struct whc_qset, list_node);
44 
45 }
46 
47 static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
48 {
49  list_move(&qset->list_node, &whc->async_list);
50  qset->in_sw_list = true;
51 }
52 
53 static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
54 {
55  struct whc_qset *next, *prev;
56 
57  qset_clear(whc, qset);
58 
59  /* Link into ASL. */
60  qset_get_next_prev(whc, qset, &next, &prev);
61  whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
62  whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
63  qset->in_hw_list = true;
64 }
65 
66 static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
67 {
68  struct whc_qset *prev, *next;
69 
70  qset_get_next_prev(whc, qset, &next, &prev);
71 
72  list_move(&qset->list_node, &whc->async_removed_list);
73  qset->in_sw_list = false;
74 
75  /*
76  * No more qsets in the ASL? The caller must stop the ASL as
77  * it's no longer valid.
78  */
79  if (list_empty(&whc->async_list))
80  return;
81 
82  /* Remove from ASL. */
83  whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
84  qset->in_hw_list = false;
85 }
86 
97 static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
98 {
99  enum whc_update update = 0;
100  uint32_t status = 0;
101 
102  while (qset->ntds) {
103  struct whc_qtd *td;
104  int t;
105 
106  t = qset->td_start;
107  td = &qset->qtd[qset->td_start];
108  status = le32_to_cpu(td->status);
109 
110  /*
111  * Nothing to do with a still active qTD.
112  */
113  if (status & QTD_STS_ACTIVE)
114  break;
115 
116  if (status & QTD_STS_HALTED) {
117  /* Ug, an error. */
118  process_halted_qtd(whc, qset, td);
119  /* A halted qTD always triggers an update
120  because the qset was either removed or
121  reactivated. */
122  update |= WHC_UPDATE_UPDATED;
123  goto done;
124  }
125 
126  /* Mmm, a completed qTD. */
127  process_inactive_qtd(whc, qset, td);
128  }
129 
130  if (!qset->remove)
131  update |= qset_add_qtds(whc, qset);
132 
133 done:
134  /*
135  * Remove this qset from the ASL if requested, but only if has
136  * no qTDs.
137  */
138  if (qset->remove && qset->ntds == 0) {
139  asl_qset_remove(whc, qset);
140  update |= WHC_UPDATE_REMOVED;
141  }
142  return update;
143 }
144 
145 void asl_start(struct whc *whc)
146 {
147  struct whc_qset *qset;
148 
149  qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
150 
151  le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
152 
154  whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
156  1000, "start ASL");
157 }
158 
159 void asl_stop(struct whc *whc)
160 {
162  whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
164  1000, "stop ASL");
165 }
166 
176 void asl_update(struct whc *whc, uint32_t wusbcmd)
177 {
178  struct wusbhc *wusbhc = &whc->wusbhc;
179  long t;
180 
181  mutex_lock(&wusbhc->mutex);
182  if (wusbhc->active) {
183  whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
184  t = wait_event_timeout(
185  whc->async_list_wq,
186  (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0,
187  msecs_to_jiffies(1000));
188  if (t == 0)
189  whc_hw_error(whc, "ASL update timeout");
190  }
191  mutex_unlock(&wusbhc->mutex);
192 }
193 
203 {
204  struct whc *whc = container_of(work, struct whc, async_work);
205  struct whc_qset *qset, *t;
206  enum whc_update update = 0;
207 
208  spin_lock_irq(&whc->lock);
209 
210  /*
211  * Transerve the software list backwards so new qsets can be
212  * safely inserted into the ASL without making it non-circular.
213  */
215  if (!qset->in_hw_list) {
216  asl_qset_insert(whc, qset);
217  update |= WHC_UPDATE_ADDED;
218  }
219 
220  update |= process_qset(whc, qset);
221  }
222 
223  spin_unlock_irq(&whc->lock);
224 
225  if (update) {
227  if (update & WHC_UPDATE_REMOVED)
228  wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
229  asl_update(whc, wusbcmd);
230  }
231 
232  /*
233  * Now that the ASL is updated, complete the removal of any
234  * removed qsets.
235  *
236  * If the qset was to be reset, do so and reinsert it into the
237  * ASL if it has pending transfers.
238  */
239  spin_lock_irq(&whc->lock);
240 
242  qset_remove_complete(whc, qset);
243  if (qset->reset) {
244  qset_reset(whc, qset);
245  if (!list_empty(&qset->stds)) {
246  asl_qset_insert_begin(whc, qset);
247  queue_work(whc->workqueue, &whc->async_work);
248  }
249  }
250  }
251 
252  spin_unlock_irq(&whc->lock);
253 }
254 
265 int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
266 {
267  struct whc_qset *qset;
268  int err;
269  unsigned long flags;
270 
271  spin_lock_irqsave(&whc->lock, flags);
272 
273  err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
274  if (err < 0) {
275  spin_unlock_irqrestore(&whc->lock, flags);
276  return err;
277  }
278 
279  qset = get_qset(whc, urb, GFP_ATOMIC);
280  if (qset == NULL)
281  err = -ENOMEM;
282  else
283  err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
284  if (!err) {
285  if (!qset->in_sw_list && !qset->remove)
286  asl_qset_insert_begin(whc, qset);
287  } else
288  usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
289 
290  spin_unlock_irqrestore(&whc->lock, flags);
291 
292  if (!err)
293  queue_work(whc->workqueue, &whc->async_work);
294 
295  return err;
296 }
297 
308 int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
309 {
310  struct whc_urb *wurb = urb->hcpriv;
311  struct whc_qset *qset = wurb->qset;
312  struct whc_std *std, *t;
313  bool has_qtd = false;
314  int ret;
315  unsigned long flags;
316 
317  spin_lock_irqsave(&whc->lock, flags);
318 
319  ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
320  if (ret < 0)
321  goto out;
322 
323  list_for_each_entry_safe(std, t, &qset->stds, list_node) {
324  if (std->urb == urb) {
325  if (std->qtd)
326  has_qtd = true;
327  qset_free_std(whc, std);
328  } else
329  std->qtd = NULL; /* so this std is re-added when the qset is */
330  }
331 
332  if (has_qtd) {
333  asl_qset_remove(whc, qset);
334  wurb->status = status;
335  wurb->is_async = true;
336  queue_work(whc->workqueue, &wurb->dequeue_work);
337  } else
338  qset_remove_urb(whc, qset, urb, status);
339 out:
340  spin_unlock_irqrestore(&whc->lock, flags);
341 
342  return ret;
343 }
344 
348 void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
349 {
350  qset->remove = 1;
351  queue_work(whc->workqueue, &whc->async_work);
352  qset_delete(whc, qset);
353 }
354 
361 int asl_init(struct whc *whc)
362 {
363  struct whc_qset *qset;
364 
365  qset = qset_alloc(whc, GFP_KERNEL);
366  if (qset == NULL)
367  return -ENOMEM;
368 
369  asl_qset_insert_begin(whc, qset);
370  asl_qset_insert(whc, qset);
371 
372  return 0;
373 }
374 
380 void asl_clean_up(struct whc *whc)
381 {
382  struct whc_qset *qset;
383 
384  if (!list_empty(&whc->async_list)) {
385  qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
386  list_del(&qset->list_node);
387  qset_free(whc, qset);
388  }
389 }