uhci.c

来自「这是一个开放源代码的与WINNT/WIN2K/WIN2003兼容的操作系统」· C语言 代码 · 共 2,164 行 · 第 1/5 页

C
2,164
字号
        //re-append them to the pending_endp_list
        ListFirst(&temp_list, pthis);
        RemoveEntryList(&temp_list);
        MergeList(&uhci->pending_endp_list, pthis);
    }
    unlock_pending_endp_list(&uhci->pending_endp_list_lock);

    if (IsListEmpty(&abort_list) == FALSE)
    {
        PLIST_ENTRY pthis;
        cancel_list = (PLIST_ENTRY) usb_alloc_mem(NonPagedPool, sizeof(WORK_QUEUE_ITEM) + sizeof(LIST_ENTRY));
        ASSERT(cancel_list);

        ListFirst(&abort_list, pthis);
        RemoveEntryList(&abort_list);
        InsertTailList(pthis, cancel_list);

        pwork_item = (PWORK_QUEUE_ITEM) & cancel_list[1];

        // we do not need to worry the uhci_cancel_pending_endp_urb running when the
        // driver is unloading since it will prevent the dev_mgr to quit till all the
        // reference count to the dev drop to zero.
        ExInitializeWorkItem(pwork_item, uhci_cancel_pending_endp_urb, (PVOID) cancel_list);
        ExQueueWorkItem(pwork_item, DelayedWorkQueue);
    }
    return TRUE;
}

NTSTATUS
uhci_submit_urb(PUHCI_DEV uhci, PUSB_DEV pdev, PUSB_ENDPOINT pendp, PURB purb)
{
    int i;
    PUHCI_PENDING_ENDP pending_endp;
    NTSTATUS status;
    USE_BASIC_IRQL;

    if (uhci == NULL || pdev == NULL || pendp == NULL || purb == NULL)
    {
        uhci_dbg_print(DBGLVL_MEDIUM,
                   ("uhci_submit_urb(): uhci=0x%x, pdev=0x%x, pendp=0x%x, purb=0x%x "
                   "called with invalid param!\n", uhci, pdev, pendp, purb));
        return STATUS_INVALID_PARAMETER;
    }

    lock_pending_endp_list(&uhci->pending_endp_list_lock);
    lock_dev(pdev, TRUE);

    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        status = purb->status = STATUS_DEVICE_DOES_NOT_EXIST;
        goto LBL_OUT;
    }

    if (dev_class(pdev) == USB_DEV_CLASS_ROOT_HUB)
    {
        unlock_dev(pdev, TRUE);
        unlock_pending_endp_list(&uhci->pending_endp_list_lock);
        status = uhci_rh_submit_urb(pdev, purb);
        return status;
    }

    if (pendp)
        purb->pendp = pendp;
    else
        purb->pendp = &pdev->default_endp;

    if (dev_from_endp(purb->pendp) != pdev)
    {
        uhci_dbg_print(DBGLVL_MEDIUM,
                   ("uhci_submit_urb(): dev_from_endp=0x%x\n, pdev=0x%x, pendp=0x%x "
                   "devices mismatch!\n", dev_from_endp(purb->pendp), pdev, pendp));

        status = purb->status = STATUS_INVALID_PARAMETER;
        goto LBL_OUT;
    }

    if (endp_state(purb->pendp) == USB_ENDP_FLAG_STALL)
    {
        status = purb->status = USB_STATUS_ENDPOINT_HALTED;
        goto LBL_OUT;
    }

    purb->pdev = pdev;
    purb->rest_bytes = purb->data_length;

    if (endp_type(purb->pendp) == USB_ENDPOINT_XFER_BULK)
        purb->bytes_to_transfer = (purb->data_length > purb->pendp->pusb_endp_desc->wMaxPacketSize * UHCI_MAX_TDS_PER_TRANSFER ? purb->pendp->pusb_endp_desc->wMaxPacketSize * UHCI_MAX_TDS_PER_TRANSFER : purb->data_length);  //multiple transfer for large data block
    else
        purb->bytes_to_transfer = purb->data_length;

    uhci_dbg_print(DBGLVL_MEDIUM, ("uhci_submit_urb(): bytes_to_transfer=0x%x\n", purb->bytes_to_transfer));

    purb->bytes_transfered = 0;
    InitializeListHead(&purb->trasac_list);
    purb->last_finished_td = &purb->trasac_list;
    purb->flags &= ~(URB_FLAG_STATE_MASK | URB_FLAG_IN_SCHEDULE | URB_FLAG_FORCE_CANCEL);
    purb->flags |= URB_FLAG_STATE_PENDING;


    i = IsListEmpty(&pendp->urb_list);
    InsertTailList(&pendp->urb_list, &purb->urb_link);

    pdev->ref_count++;          //for urb reference

    if (i == FALSE)
    {
        //there is urb pending, simply queue it and return
        status = purb->status = STATUS_PENDING;
        goto LBL_OUT;
    }
    else if (usb_endp_busy_count(purb->pendp) && endp_type(purb->pendp) != USB_ENDPOINT_XFER_ISOC)
    {
        //
        //No urb waiting but urb overlap not allowed, 
        //so leave it in queue and return, will be scheduled
        //later
        //
        status = purb->status = STATUS_PENDING;
        goto LBL_OUT;
    }

    pending_endp = alloc_pending_endp(&uhci->pending_endp_pool, 1);
    if (pending_endp == NULL)
    {
        //panic
        status = purb->status = STATUS_UNSUCCESSFUL;
        goto LBL_OUT2;
    }

    pending_endp->pendp = purb->pendp;
    InsertTailList(&uhci->pending_endp_list, (PLIST_ENTRY) pending_endp);

    unlock_dev(pdev, TRUE);
    unlock_pending_endp_list(&uhci->pending_endp_list_lock);

    uhci_process_pending_endp(uhci);
    return STATUS_PENDING;

LBL_OUT2:
    pdev->ref_count--;
    RemoveEntryList((PLIST_ENTRY) purb);

LBL_OUT:
    unlock_dev(pdev, TRUE);
    unlock_pending_endp_list(&uhci->pending_endp_list_lock);
    return status;
}

NTSTATUS
uhci_set_error_code(PURB urb, ULONG raw_status)
{
    if ((raw_status & TD_CTRL_ANY_ERROR) == 0)
    {
        //test if the urb is canceled
        if (urb->flags & URB_FLAG_FORCE_CANCEL)
            urb->status = STATUS_CANCELLED;
        else
            urb->status = STATUS_SUCCESS;
    }

    else if (raw_status & TD_CTRL_BABBLE)
        urb->status = USB_STATUS_DATA_OVERRUN;

    else if (raw_status & TD_CTRL_STALLED)
        urb->status = USB_STATUS_STALL_PID;

    else if (raw_status & TD_CTRL_DBUFERR)
        urb->status = USB_STATUS_BUFFER_OVERRUN;

    else if (raw_status & TD_CTRL_CRCTIMEO)
        urb->status = USB_STATUS_CRC;

    else if (raw_status & TD_CTRL_BITSTUFF)
        urb->status = USB_STATUS_BTSTUFF;

    else
        urb->status = STATUS_UNSUCCESSFUL;

    return urb->status;
}

BOOLEAN NTAPI
uhci_sync_remove_urb_finished(PVOID context)
{
    PUHCI_DEV uhci;
    PLIST_ENTRY pthis, pnext, ptemp;
    PURB purb;
    PSYNC_PARAM pparam;

    pparam = (PSYNC_PARAM) context;
    uhci = pparam->uhci;
    ptemp = (PLIST_ENTRY) pparam->context;

    if (uhci == NULL)
    {
        return (UCHAR) (pparam->ret = FALSE);
    }

    ListFirst(&uhci->urb_list, pthis);
    while (pthis)
    {
        //remove urbs not in the schedule
        ListNext(&uhci->urb_list, pthis, pnext);
        purb = (PURB) pthis;

        if ((purb->flags & URB_FLAG_IN_SCHEDULE) == 0)
        {
            //finished or canceled( not apply for split bulk ).
            purb->flags &= ~URB_FLAG_STATE_MASK;
            purb->flags |= URB_FLAG_STATE_FINISHED;
            RemoveEntryList(pthis);
            InsertTailList(ptemp, pthis);
        }
        pthis = pnext;
    }
    pparam->ret = TRUE;
    return (UCHAR) TRUE;
}

BOOLEAN
uhci_drop_fsbr(PUHCI_DEV uhci)
{
    if (uhci == NULL)
        return (UCHAR) FALSE;

    uhci->fsbr_cnt--;

    if (uhci->fsbr_cnt <= 0)
    {
        uhci->skel_term_qh->link = UHCI_PTR_TERM;
        uhci->fsbr_cnt = 0;
    }

    return (UCHAR) TRUE;
}

VOID NTAPI
uhci_dpc_callback(PKDPC dpc, PVOID context, PVOID sysarg1, PVOID sysarg2)
{
    PUHCI_DEV uhci;

    LIST_HEAD temp_list;
    PLIST_ENTRY pthis, pnext;
    PURB purb;
    PQH_EXTENSION pqhe;
    PUHCI_PENDING_ENDP pending_endp;
    PUSB_DEV pdev;
    PUSB_ENDPOINT pendp;

    BOOLEAN finished;
    LONG i, j;
    ULONG uhci_status, urb_status, toggle = 0;

    SYNC_PARAM sync_param;
    USE_BASIC_NON_PENDING_IRQL;

    UNREFERENCED_PARAMETER(dpc);
    UNREFERENCED_PARAMETER(sysarg2);

    uhci = (PUHCI_DEV) context;
    if (uhci == NULL)
        return;

    uhci_status = (ULONG) sysarg1;

    InitializeListHead(&temp_list);

    sync_param.uhci = uhci;
    sync_param.context = (PVOID) & temp_list;

    uhci_dbg_print(DBGLVL_MAXIMUM, ("uhci_dpc_callback(): entering..., uhci=0x%x\n", uhci));
    //remove finished urb from uhci's urb-list 
    KeSynchronizeExecution(uhci->pdev_ext->uhci_int, uhci_sync_remove_urb_finished, &sync_param);

    //release resources( tds, and qhs ) the urb occupied
    while (IsListEmpty(&temp_list) == FALSE)
    {
        //not in any public queue, if do not access into dev, no race 
        //condition will occur
        purb = (PURB) RemoveHeadList(&temp_list);
        urb_status = purb->status;

        //the only place we do not use this lock on non-pending-endp-list data ops
        KeAcquireSpinLockAtDpcLevel(&uhci->pending_endp_list_lock);
        while (IsListEmpty(&purb->trasac_list) == FALSE)
        {
            pthis = RemoveHeadList(&purb->trasac_list);

            if ((((PTD_EXTENSION) pthis)->flags & UHCI_ITEM_FLAG_TYPE) == UHCI_ITEM_FLAG_QH)
            {
                pqhe = (PQH_EXTENSION) pthis;
                lock_qh_pool(&uhci->qh_pool, TRUE);
                free_qh(&uhci->qh_pool, pqhe->pqh);
                unlock_qh_pool(&uhci->qh_pool, TRUE);
            }
            else
            {
                //must be a td chain
                InsertHeadList(&purb->trasac_list, pthis);
                for(i = 0, purb->bytes_transfered = 0; i < purb->td_count; i++)
                {
                    PUHCI_TD ptd;
                    // accumulate data transfered in tds
                    ptd = ((PTD_EXTENSION) pthis)->ptd;
                    if ((ptd->status & TD_CTRL_ACTIVE) == 0 && (ptd->status & TD_CTRL_ANY_ERROR) == 0)
                    {
                        j = ptd->status & 0x7ff;
                        purb->bytes_transfered += ((j == 0x7ff) ? 0 : (j + 1));

                    }
                    ListNext(&purb->trasac_list, pthis, pnext);
                    pthis = pnext;
                }

                if (urb_status & TD_CTRL_ANY_ERROR)
                {
                    if (purb->last_finished_td != NULL && purb->last_finished_td != &purb->trasac_list)
                        toggle = (((PTD_EXTENSION) purb->last_finished_td)->ptd->info & (1 << 19));
                }
                //trick, remove trasac_list
                ListFirst(&purb->trasac_list, pthis);
                RemoveEntryList(&purb->trasac_list);
                lock_td_pool(&uhci->td_pool, TRUE);
                free_tds(&uhci->td_pool, ((PTD_EXTENSION) pthis)->ptd);
                unlock_td_pool(&uhci->td_pool, TRUE);
                //termination condition
                InitializeListHead(&purb->trasac_list);
                purb->last_finished_td = NULL;
            }
        }

        if (endp_type(purb->pendp) == USB_ENDPOINT_XFER_ISOC
            || endp_type(purb->pendp) == USB_ENDPOINT_XFER_INT)
            uhci_claim_bandwidth(uhci, purb, FALSE);    //release band-width

        KeReleaseSpinLockFromDpcLevel(&uhci->pending_endp_list_lock);

        uhci_set_error_code(purb, urb_status);

        finished = TRUE;

        //since the ref_count for the urb is not released, we can safely have one
        //pointer to dev
        pdev = dev_from_endp(purb->pendp);
        pendp = purb->pendp;

        if (purb->status == USB_STATUS_BABBLE_DETECTED)
        {
            usb_dbg_print(DBGLVL_MEDIUM,
                          ("uhci_dpc_callback(): alert!!!, babble detected, severe error, reset the whole bus\n"));
            uhci_reset(uhci);
            uhci_start(&uhci->hcd_interf);
        }

        //this will let the new request in uhci_generic_urb_completion to this endp
        //be processed rather than queued in the pending_endp_list
        lock_dev(pdev, TRUE);
        usb_endp_busy_count_dec(pendp);
        unlock_dev(pdev, TRUE);

        if (usb_success(purb->status) == FALSE)
        {
            // set error code and complete the urb and purb is invalid from this point
            uhci_generic_urb_completion(purb, purb->context);
        }
        else
        {
            if ((purb->pipe & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)
            {
                purb->rest_bytes -= purb->bytes_transfered;
                if (purb->rest_bytes)
                {
                    finished = FALSE;
                }
                else
                {
                    uhci_generic_urb_completion(purb, purb->context);
                }
            }
            else
            {
                uhci_generic_urb_completion(purb, purb->context);
                //purb is now invalid
            }
        }

        KeAcquireSpinLockAtDpcLevel(&uhci->pending_endp_list_lock);
        lock_dev(pdev, TRUE);

        if (finished)
            pdev->ref_count--;

        if (urb_status & TD_CTRL_ANY_ERROR && endp_type(pendp) != USB_ENDPOINT_XFER_CONTROL)
        {
            pendp->flags &= ~USB_ENDP_FLAG_STAT_MASK;
            pendp->flags |= USB_ENDP_FLAG_STALL;
        }

        if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
        {
            unlock_dev(pdev, TRUE);
            KeReleaseSpinLockFromDpcLevel(&uhci->pending_endp_list_lock);
            if (finished == FALSE)
            {

                purb->status = STATUS_DEVICE_DOES_NOT_EXIST;
                uhci_generic_urb_completion(purb, purb->context);

                lock_dev(pdev, TRUE);
                pdev->ref_count--;
                unlock_dev(pdev, TRUE);
            }
            continue;
        }

        if (finished && IsListEmpty(&pendp->urb_list) == TRUE)
        {
            unlock_dev(pdev, TRUE);
            KeReleaseSpinLockFromDpcLevel(&uhci->pending_endp_list_lock);
            continue;
        }
        else if (finished == TRUE)
        {
            //has urb in the endp's urb-list
            if (usb_endp_busy_count(pendp) > 0)
            {
                //the urbs still have chance to be sheduled but not this time
                unlock_dev(pdev, TRUE);
                KeReleaseSpinLockFromDpcLevel(&uhci->pending_endp_list_lock);
                continue;
            }
        }

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?