Выборочная приостановка USB

Замечание

Эта статья предназначена для разработчиков драйверов устройств. Если у вас возникли проблемы с USB-устройством, см. статью "Устранение проблем с USB-C в Windows"

Функция выборочной приостановки USB позволяет драйверу концентратора приостановить отдельный порт, не влияя на работу других портов в концентраторе. Эта функция полезна на переносимых компьютерах, так как она помогает экономии заряда батареи. Во многих устройствах, таких как биометрические сканеры, требуется только питание периодически. Приостановка таких устройств, когда они не используются, сокращает общее потребление электроэнергии. Более важно, что любое устройство, которое не приостановлено выборочно, может помешать USB контроллеру отключить график передачи, находящийся в системной памяти. Прямой доступ к памяти (DMA) контроллером к планировщику может предотвратить переход процессоров системы в более глубокие состояния сна, такие как C3.

Выборочная приостановка включена по умолчанию. Корпорация Майкрософт настоятельно рекомендует не отключать выборочную приостановку.

Драйверы клиентов не должны пытаться определить, включена ли выборочная приостановка перед отправкой запросов на простой. Они должны отправлять запросы бездействия всякий раз, когда устройство неактивно. Если запрос на простой завершается сбоем, драйвер клиента должен сбросить таймер простоя и повторить попытку.

Для выборочной приостановки USB-устройства существуют два разных механизма: простой запрос irPs (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) и установка параметров irPs питания (IRP_MN_SET_POWER). Используемый механизм зависит от типа устройства: составного или некомпосита.

Выбор механизма выборочной приостановки

Клиентские драйверы для интерфейса на составном устройстве, которые активируют интерфейс для удаленного пробуждения с использованием IRP ожидания (IRP_MN_WAIT_WAKE), должны использовать механизм запроса бездействия IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) для выборочной приостановки устройства.

Сведения об удаленном пробуждении см. в следующем разделе:

В этом разделе объясняется механизм выборочной приостановки Windows.

Отправка IRP запроса на бездействие USB

Когда устройство неактивно, драйвер клиента сообщает драйверу шины, отправив IRP-запрос на бездействие (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). После того как драйвер шины определяет, что устройство безопасно перевести в режим низкого энергопотребления, он вызывает процедуру обратного вызова, которую драйвер клиентского устройства передал по стеку вызовов вместе с IRP на простой режим.

В подпрограмме обратного вызова драйвер клиента должен отменить все незавершенные операции ввода-вывода и дождаться завершения всех IRP ввода-вывода USB. Затем он может выдать запрос IRP_MN_SET_POWER для изменения состояния питания устройства WDM на D2. Подпрограмма обратного вызова должна ожидать завершения запроса D2 перед возвратом. Дополнительные сведения о процедуре обратного вызова уведомления о бездействии см. в разделе "Реализация процедуры обратного вызова IRP для запроса простоя USB".

Водитель автобуса не завершает запрос IRP на бездействие после вызова подпрограммы обратного вызова уведомления о бездействии. Вместо этого драйвер шины удерживает неактивный запрос IRP до тех пор, пока не будет выполнено одно из следующих условий:

  • Получен IRP_MN_SURPRISE_REMOVAL или IRP_MN_REMOVE_DEVICE IRP. При получении одного из этих запросов IRP, запрос простоя завершается со статусом STATUS_CANCELLED.
  • Драйвер шины получает запрос на перевод устройства в рабочее состояние питания (D0). После получения этого запроса драйвер шины завершает ожидающий запрос простоя IRP с STATUS_SUCCESS.

Следующие ограничения применяются к использованию бездействующего irps запроса:

  • Драйверы должны находиться в состоянии питания устройства D0 при отправке IRP для запроса на бездействие.
  • Драйверы должны отправлять только один простой запрос IRP на каждый стек устройств.

В следующем примере кода WDM демонстрируются шаги, которые выполняет драйвер устройства для отправки запроса IRP для USB в состоянии простоя. Проверка ошибок опущена в следующем примере кода.

  1. Выделите и инициализируйте IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION IRP

    irp = IoAllocateIrp (DeviceContext->TopOfStackDeviceObject->StackSize, FALSE);
    nextStack = IoGetNextIrpStackLocation (irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION;
    nextStack->Parameters.DeviceIoControl.InputBufferLength =
    sizeof(struct _USB_IDLE_CALLBACK_INFO);
    
  2. Выделите и инициализируйте структуру информации о простое запроса (USB_IDLE_CALLBACK_INFO).

    idleCallbackInfo = ExAllocatePool (NonPagedPool,
    sizeof(struct _USB_IDLE_CALLBACK_INFO));
    idleCallbackInfo->IdleCallback = IdleNotificationCallback;
    // Put a pointer to the device extension in member IdleContext
    idleCallbackInfo->IdleContext = (PVOID) DeviceExtension;
    nextStack->Parameters.DeviceIoControl.Type3InputBuffer = idleCallbackInfo;
    
  3. Задайте процедуру завершения.

    Драйвер клиента должен ассоциировать подпрограмму завершения с IRP запроса простоя. Дополнительные сведения о подпрограмме завершения простоя уведомлений и примере кода см. в разделе "Реализация процедуры завершения запроса на простой USB".

    IoSetCompletionRoutine (irp,
        IdleNotificationRequestComplete,
        DeviceContext,
        TRUE,
        TRUE,
        TRUE);
    
  4. Сохраните незадействованный запрос в расширении устройства.

    deviceExtension->PendingIdleIrp = irp;
    
  5. Отправьте запрос простоя родительскому драйверу.

    ntStatus = IoCallDriver (DeviceContext->TopOfStackDeviceObject, irp);
    

Отмена запроса на неактивность USB

В определенных обстоятельствах водителю устройства может потребоваться отменить запрос на простой IRP, отправленный водителю автобуса. Эта ситуация может произойти, если устройство удалено, активируется после простоя и отправки запроса на простой или если вся система переходит в более низкое состояние питания системы.

Драйвер клиента отменяет неактивный IRP путем вызова IoCancelIrp. В следующей таблице описаны три сценария отмены бездействия IRP и указываются действия, которые должен предпринять драйвер:

Сценарий Механизм отмены неактивных запросов
Драйвер клиента отменяет простаивающий IRP, а подпрограмма обратного вызова USB-уведомлений бездействия не была вызвана. Стек USB-драйверов завершает неактивный IRP. Так как устройство никогда не покидает D0, драйвер не изменяет состояние устройства.
Драйвер клиента отменяет неактивный IRP, стек USB-драйверов вызывает подпрограмму уведомления о бездействии USB, и она еще не завершилась. Возможно, что подпрограмма обратного вызова бездействия USB вызывается, даже если клиентский драйвер вызвал отмену IRP. В этом случае подпрограмма обратного вызова драйвера клиента должна по-прежнему выключить устройство, переведя его в более низкое состояние питания синхронно.

Когда устройство находится в нижнем состоянии питания, драйвер клиента может отправить запрос D0 .

Кроме того, драйвер может ждать завершения простоя IRP в стеке USB-драйверов, а затем отправить IRP D0 .

Если подпрограмма обратного вызова не может поместить устройство в состояние низкого энергопотребления из-за нехватки памяти для выделения IRP питания, она должна отменить IRP ожидания и немедленно завершить работу. Неактивный IRP не завершается до тех пор, пока функция обратного вызова не вернется. Поэтому подпрограмма обратного вызова не должна блокировать ожидание завершения отмененной неактивной IRP.
Устройство уже находится в состоянии низкой мощности. Если устройство уже находится в состоянии низкой мощности, драйвер клиента может отправить IRP D0 . Стек USB-драйверов завершает простой запрос IRP с STATUS_SUCCESS.

Кроме того, драйвер может отменить бездействующий IRP, подождать, пока стек USB-драйверов завершит бездействующий IRP, а затем отправить D0 IRP.

Процедура завершения IRP запроса на простое состояние USB

Во многих случаях водитель автобуса может вызвать процедуру завершения запроса бездействия водителя IRP. При возникновении этой ситуации клиентский драйвер должен определить, почему драйвер шины завершил IRP. Возвращенный код состояния может предоставить эти сведения. Если код состояния не STATUS_POWER_STATE_INVALID, драйвер должен поместить свое устройство в D0 если устройство еще не в D0. Если устройство по-прежнему неактивно, драйвер может отправить другой IRP-запрос на простой.

Замечание

Подпрограмма завершения бездействия IRP не должна блокировать процесс ожидания, пока не завершится запрос на питание D0. Подпрограмма завершения может вызываться в контексте power IRP драйвером концентратора, а блокировка другого power IRP в подпрограмме завершения может привести к взаимоблокировке.

В следующем списке показано, как подпрограмма завершения для неактивного запроса должна интерпретировать некоторые распространенные коды состояния:

Код состояния Описание
Статус: Успешно Указывает, что устройство больше не должно быть приостановлено. Однако драйверы должны убедиться, что их устройства работают, и поместить их в D0 , если они еще не находятся в D0.
СТАТУС_ОТМЕНЕН Водитель автобуса завершает запрос IRP бездействия с STATUS_CANCELLED в любом из следующих обстоятельств:
  • Драйвер устройства отменил IRP.
  • Требуется изменение состояния питания системы.
НЕВЕРНОЕ СОСТОЯНИЕ ЭНЕРГОПОТРЕБЛЕНИЯ Указывает, что драйвер устройства запрашивал состояние питания D3 для своего устройства. Когда этот запрос происходит, драйвер шины завершает все необработанные IRPs со статусом STATUS_POWER_STATE_INVALID.
СТАТУС_УСТРОЙСТВО_ЗАНЯТО Указывает, что драйвер шины уже содержит запрос на простое IRP, ожидающий обработки устройством. Только один находящийся в бездействии IRP может быть ожидающим одновременно для данного устройства. Отправка нескольких запросов на простой IRP является ошибкой со стороны владельца политики энергопотребления. Автор драйвера устраняет ошибку.

В следующем примере кода представлена реализация процедуры завершения ожидания запроса.

/*
Routine Description:
  Completion routine for idle notification IRP

Arguments:
    DeviceObject - pointer to device object
    Irp - I/O request packet
    DeviceExtension - pointer to device extension

Return Value:
    NT status value
--*/

NTSTATUS
IdleNotificationRequestComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PDEVICE_EXTENSION DeviceExtension
    )
{
    NTSTATUS                ntStatus;
    POWER_STATE             powerState;
    PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;

    ntStatus = Irp->IoStatus.Status;

    if(!NT_SUCCESS(ntStatus) && ntStatus != STATUS_NOT_SUPPORTED)
    {

        //Idle IRP completes with error.
        switch(ntStatus)
        {
        case STATUS_INVALID_DEVICE_REQUEST:
            //Invalid request.
            break;

        case STATUS_CANCELLED:
            //1. The device driver canceled the IRP.
            //2. A system power state change is required.
            break;

        case STATUS_POWER_STATE_INVALID:
            // Device driver requested a D3 power state for its device
            // Release the allocated resources.
            goto IdleNotificationRequestComplete_Exit;

        case STATUS_DEVICE_BUSY:
            //The bus driver already holds an idle IRP pending for the device.
            break;

        default:
            break;
        }

        // If IRP completes with error, issue a SetD0
        //Increment the I/O count because
        //a new IRP is dispatched for the driver.
        //This call is not shown.
        powerState.DeviceState = PowerDeviceD0;

        // Issue a new IRP
        PoRequestPowerIrp (
            DeviceExtension->PhysicalDeviceObject,
            IRP_MN_SET_POWER,
            powerState,
            (PREQUEST_POWER_COMPLETE) PoIrpCompletionFunc,
            DeviceExtension,
            NULL);
    }

IdleNotificationRequestComplete_Exit:

    idleCallbackInfo = DeviceExtension->IdleCallbackInfo;
    DeviceExtension->IdleCallbackInfo = NULL;
    DeviceExtension->PendingIdleIrp = NULL;
    InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

    if(idleCallbackInfo)
    {
        ExFreePool(idleCallbackInfo);
    }

    DeviceExtension->IdleState = IdleComplete;

    // Because the IRP was created using IoAllocateIrp,
    // the IRP needs to be released by calling IoFreeIrp.
    // Also return STATUS_MORE_PROCESSING_REQUIRED so that
    // the kernel does not reference this.
    IoFreeIrp(Irp);
    KeSetEvent(&DeviceExtension->IdleIrpCompleteEvent, IO_NO_INCREMENT, FALSE);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

Подпрограмма обратного вызова бездействия USB

Водитель шины (или экземпляр драйвера USB-хаба, либо родительский драйвер) определяет, когда безопасно приостанавливать подчиненные устройства. Если это так, он вызывает подпрограмму обратного вызова уведомления о простое, предоставляемую драйвером клиента каждого отдельного дочернего устройства.

Прототип функции для USB_IDLE_CALLBACK выглядит следующим образом:

typedef VOID (*USB_IDLE_CALLBACK)(__in PVOID Context);

Драйвер устройства должен выполнить следующие действия в обработчике обратного вызова уведомлений о бездействии:

  • Запросите IRP_MN_WAIT_WAKE IRP для устройства, если устройство должно быть вооружено для удаленного пробуждения.
  • Отмените все операции ввода-вывода и подготовьте устройство для перехода к более низкому состоянию питания.
  • Поместите устройство в состояние сна WDM, вызвав PoRequestPowerIrp с параметром PowerState, установленным на значение PowerDeviceD2 (определённого в wdm.h; ntddk.h).

Драйвер концентратора и USB Generic Parent Driver (Usbccgp.sys) вызывают подпрограмму обратного вызова уведомления о режиме ожидания при IRQL = PASSIVE_LEVEL. Затем подпрограмма обратного вызова может блокироваться, пока она ожидает завершения запроса на изменение состояния питания.

Подпрограмма обратного вызова вызывается только в то время как система находится в S0 , а устройство находится в D0.

Следующие ограничения применяются к подпрограммам обратного вызова уведомлений о простое запросов:

  • Драйверы устройств могут инициировать переход состояния питания устройства с D0 на D2 в подпрограмме обратного вызова уведомлений об простое, но другие переходы состояния питания не разрешены. В частности, драйвер не должен пытаться изменить устройство на D0 при выполнении процедуры обратного вызова.
  • Драйверы устройств не должны запрашивать более одного power IRP из процедуры обратного вызова уведомления о простое.

Подготовка устройств для пробуждения в подпрограмме обратного вызова уведомлений о бездействии

Подпрограмма обратного вызова при бездействии должна определить, есть ли в ожидании запрос IRP_MN_WAIT_WAKE для устройства. Если запрос IRP_MN_WAIT_WAKE не находится в ожидании, функция обратного вызова должна отправить запрос IRP_MN_WAIT_WAKE перед приостановкой устройства. Дополнительные сведения о механизме ожидания и пробуждения см. в разделе "Поддержка устройств с возможностями пробуждения".

Глобальная приостановка USB

Спецификация USB 2.0 определяет глобальную приостановку как приостановку всей шины за контроллером USB-узла путем прекращения всего USB-трафика на шине, включая пакеты начала кадра. Подчиненные устройства, которые еще не приостановлены, обнаруживают состояние бездействия на вышестоящем порту и входят в состояние ожидания самостоятельно. Windows не реализует глобальную приостановку таким образом. Windows всегда выборочно приостанавливает каждое USB-устройство за контроллером USB-узла, прежде чем прекратить весь USB-трафик на шине.

Условия глобальной приостановки

Драйвер USB-хаба выборочно приостанавливает любой хаб, если все подключенные устройства находятся в состоянии питания D1, D2 или D3. Вся шина входит в глобальную приостановку после выборочного приостановления всех USB-концентраторов. Стек USB-драйверов рассматривает устройство как простоящее, когда устройство находится в состоянии устройства WDM D1, D2 или D3.