linux/drivers/usb/host/whci/asl.c
<<
>>
Prefs
   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/dma-mapping.h>
  20#include <linux/uwb/umc.h>
  21#include <linux/usb.h>
  22
  23#include "../../wusbcore/wusbhc.h"
  24
  25#include "whcd.h"
  26
  27static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
  28                               struct whc_qset **next, struct whc_qset **prev)
  29{
  30        struct list_head *n, *p;
  31
  32        BUG_ON(list_empty(&whc->async_list));
  33
  34        n = qset->list_node.next;
  35        if (n == &whc->async_list)
  36                n = n->next;
  37        p = qset->list_node.prev;
  38        if (p == &whc->async_list)
  39                p = p->prev;
  40
  41        *next = container_of(n, struct whc_qset, list_node);
  42        *prev = container_of(p, struct whc_qset, list_node);
  43
  44}
  45
  46static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
  47{
  48        list_move(&qset->list_node, &whc->async_list);
  49        qset->in_sw_list = true;
  50}
  51
  52static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
  53{
  54        struct whc_qset *next, *prev;
  55
  56        qset_clear(whc, qset);
  57
  58        /* Link into ASL. */
  59        qset_get_next_prev(whc, qset, &next, &prev);
  60        whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
  61        whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
  62        qset->in_hw_list = true;
  63}
  64
  65static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
  66{
  67        struct whc_qset *prev, *next;
  68
  69        qset_get_next_prev(whc, qset, &next, &prev);
  70
  71        list_move(&qset->list_node, &whc->async_removed_list);
  72        qset->in_sw_list = false;
  73
  74        /*
  75         * No more qsets in the ASL?  The caller must stop the ASL as
  76         * it's no longer valid.
  77         */
  78        if (list_empty(&whc->async_list))
  79                return;
  80
  81        /* Remove from ASL. */
  82        whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
  83        qset->in_hw_list = false;
  84}
  85
  86/**
  87 * process_qset - process any recently inactivated or halted qTDs in a
  88 * qset.
  89 *
  90 * After inactive qTDs are removed, new qTDs can be added if the
  91 * urb queue still contains URBs.
  92 *
  93 * Returns any additional WUSBCMD bits for the ASL sync command (i.e.,
  94 * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed).
  95 */
  96static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
  97{
  98        enum whc_update update = 0;
  99        uint32_t status = 0;
 100
 101        while (qset->ntds) {
 102                struct whc_qtd *td;
 103                int t;
 104
 105                t = qset->td_start;
 106                td = &qset->qtd[qset->td_start];
 107                status = le32_to_cpu(td->status);
 108
 109                /*
 110                 * Nothing to do with a still active qTD.
 111                 */
 112                if (status & QTD_STS_ACTIVE)
 113                        break;
 114
 115                if (status & QTD_STS_HALTED) {
 116                        /* Ug, an error. */
 117                        process_halted_qtd(whc, qset, td);
 118                        /* A halted qTD always triggers an update
 119                           because the qset was either removed or
 120                           reactivated. */
 121                        update |= WHC_UPDATE_UPDATED;
 122                        goto done;
 123                }
 124
 125                /* Mmm, a completed qTD. */
 126                process_inactive_qtd(whc, qset, td);
 127        }
 128
 129        if (!qset->remove)
 130                update |= qset_add_qtds(whc, qset);
 131
 132done:
 133        /*
 134         * Remove this qset from the ASL if requested, but only if has
 135         * no qTDs.
 136         */
 137        if (qset->remove && qset->ntds == 0) {
 138                asl_qset_remove(whc, qset);
 139                update |= WHC_UPDATE_REMOVED;
 140        }
 141        return update;
 142}
 143
 144void asl_start(struct whc *whc)
 145{
 146        struct whc_qset *qset;
 147
 148        qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
 149
 150        le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
 151
 152        whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN);
 153        whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
 154                      WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED,
 155                      1000, "start ASL");
 156}
 157
 158void asl_stop(struct whc *whc)
 159{
 160        whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0);
 161        whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
 162                      WUSBSTS_ASYNC_SCHED, 0,
 163                      1000, "stop ASL");
 164}
 165
 166/**
 167 * asl_update - request an ASL update and wait for the hardware to be synced
 168 * @whc: the WHCI HC
 169 * @wusbcmd: WUSBCMD value to start the update.
 170 *
 171 * If the WUSB HC is inactive (i.e., the ASL is stopped) then the
 172 * update must be skipped as the hardware may not respond to update
 173 * requests.
 174 */
 175void asl_update(struct whc *whc, uint32_t wusbcmd)
 176{
 177        struct wusbhc *wusbhc = &whc->wusbhc;
 178        long t;
 179
 180        mutex_lock(&wusbhc->mutex);
 181        if (wusbhc->active) {
 182                whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
 183                t = wait_event_timeout(
 184                        whc->async_list_wq,
 185                        (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0,
 186                        msecs_to_jiffies(1000));
 187                if (t == 0)
 188                        whc_hw_error(whc, "ASL update timeout");
 189        }
 190        mutex_unlock(&wusbhc->mutex);
 191}
 192
 193/**
 194 * scan_async_work - scan the ASL for qsets to process.
 195 *
 196 * Process each qset in the ASL in turn and then signal the WHC that
 197 * the ASL has been updated.
 198 *
 199 * Then start, stop or update the asynchronous schedule as required.
 200 */
 201void scan_async_work(struct work_struct *work)
 202{
 203        struct whc *whc = container_of(work, struct whc, async_work);
 204        struct whc_qset *qset, *t;
 205        enum whc_update update = 0;
 206
 207        spin_lock_irq(&whc->lock);
 208
 209        /*
 210         * Transerve the software list backwards so new qsets can be
 211         * safely inserted into the ASL without making it non-circular.
 212         */
 213        list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) {
 214                if (!qset->in_hw_list) {
 215                        asl_qset_insert(whc, qset);
 216                        update |= WHC_UPDATE_ADDED;
 217                }
 218
 219                update |= process_qset(whc, qset);
 220        }
 221
 222        spin_unlock_irq(&whc->lock);
 223
 224        if (update) {
 225                uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB;
 226                if (update & WHC_UPDATE_REMOVED)
 227                        wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
 228                asl_update(whc, wusbcmd);
 229        }
 230
 231        /*
 232         * Now that the ASL is updated, complete the removal of any
 233         * removed qsets.
 234         *
 235         * If the qset was to be reset, do so and reinsert it into the
 236         * ASL if it has pending transfers.
 237         */
 238        spin_lock_irq(&whc->lock);
 239
 240        list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
 241                qset_remove_complete(whc, qset);
 242                if (qset->reset) {
 243                        qset_reset(whc, qset);
 244                        if (!list_empty(&qset->stds)) {
 245                                asl_qset_insert_begin(whc, qset);
 246                                queue_work(whc->workqueue, &whc->async_work);
 247                        }
 248                }
 249        }
 250
 251        spin_unlock_irq(&whc->lock);
 252}
 253
 254/**
 255 * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL).
 256 * @whc: the WHCI host controller
 257 * @urb: the URB to enqueue
 258 * @mem_flags: flags for any memory allocations
 259 *
 260 * The qset for the endpoint is obtained and the urb queued on to it.
 261 *
 262 * Work is scheduled to update the hardware's view of the ASL.
 263 */
 264int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
 265{
 266        struct whc_qset *qset;
 267        int err;
 268        unsigned long flags;
 269
 270        spin_lock_irqsave(&whc->lock, flags);
 271
 272        err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
 273        if (err < 0) {
 274                spin_unlock_irqrestore(&whc->lock, flags);
 275                return err;
 276        }
 277
 278        qset = get_qset(whc, urb, GFP_ATOMIC);
 279        if (qset == NULL)
 280                err = -ENOMEM;
 281        else
 282                err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
 283        if (!err) {
 284                if (!qset->in_sw_list && !qset->remove)
 285                        asl_qset_insert_begin(whc, qset);
 286        } else
 287                usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
 288
 289        spin_unlock_irqrestore(&whc->lock, flags);
 290
 291        if (!err)
 292                queue_work(whc->workqueue, &whc->async_work);
 293
 294        return err;
 295}
 296
 297/**
 298 * asl_urb_dequeue - remove an URB (qset) from the async list.
 299 * @whc: the WHCI host controller
 300 * @urb: the URB to dequeue
 301 * @status: the current status of the URB
 302 *
 303 * URBs that do yet have qTDs can simply be removed from the software
 304 * queue, otherwise the qset must be removed from the ASL so the qTDs
 305 * can be removed.
 306 */
 307int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
 308{
 309        struct whc_urb *wurb = urb->hcpriv;
 310        struct whc_qset *qset = wurb->qset;
 311        struct whc_std *std, *t;
 312        bool has_qtd = false;
 313        int ret;
 314        unsigned long flags;
 315
 316        spin_lock_irqsave(&whc->lock, flags);
 317
 318        ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
 319        if (ret < 0)
 320                goto out;
 321
 322        list_for_each_entry_safe(std, t, &qset->stds, list_node) {
 323                if (std->urb == urb) {
 324                        if (std->qtd)
 325                                has_qtd = true;
 326                        qset_free_std(whc, std);
 327                } else
 328                        std->qtd = NULL; /* so this std is re-added when the qset is */
 329        }
 330
 331        if (has_qtd) {
 332                asl_qset_remove(whc, qset);
 333                wurb->status = status;
 334                wurb->is_async = true;
 335                queue_work(whc->workqueue, &wurb->dequeue_work);
 336        } else
 337                qset_remove_urb(whc, qset, urb, status);
 338out:
 339        spin_unlock_irqrestore(&whc->lock, flags);
 340
 341        return ret;
 342}
 343
 344/**
 345 * asl_qset_delete - delete a qset from the ASL
 346 */
 347void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
 348{
 349        qset->remove = 1;
 350        queue_work(whc->workqueue, &whc->async_work);
 351        qset_delete(whc, qset);
 352}
 353
 354/**
 355 * asl_init - initialize the asynchronous schedule list
 356 *
 357 * A dummy qset with no qTDs is added to the ASL to simplify removing
 358 * qsets (no need to stop the ASL when the last qset is removed).
 359 */
 360int asl_init(struct whc *whc)
 361{
 362        struct whc_qset *qset;
 363
 364        qset = qset_alloc(whc, GFP_KERNEL);
 365        if (qset == NULL)
 366                return -ENOMEM;
 367
 368        asl_qset_insert_begin(whc, qset);
 369        asl_qset_insert(whc, qset);
 370
 371        return 0;
 372}
 373
 374/**
 375 * asl_clean_up - free ASL resources
 376 *
 377 * The ASL is stopped and empty except for the dummy qset.
 378 */
 379void asl_clean_up(struct whc *whc)
 380{
 381        struct whc_qset *qset;
 382
 383        if (!list_empty(&whc->async_list)) {
 384                qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
 385                list_del(&qset->list_node);
 386                qset_free(whc, qset);
 387        }
 388}
 389