Compartir a través de


Suspensión selectiva de USB

Nota:

Este artículo es para desarrolladores de controladores de dispositivos. Si tiene dificultades con un dispositivo USB, consulte Corregir problemas de USB-C en Windows.

La característica de suspensión selectiva USB permite al controlador del concentrador suspender un puerto individual sin afectar al funcionamiento de los demás puertos del concentrador. Esta funcionalidad es útil en equipos portátiles, ya que ayuda a ahorrar energía de la batería. Muchos dispositivos, como escáneres biométricos, solo requieren energía de forma intermitente. Suspender estos dispositivos, cuando no están en uso, reduce el consumo general de energía. Lo más importante es que cualquier dispositivo que no esté suspendido selectivamente podría impedir que el controlador de host USB deshabilite su programación de transferencia, que reside en la memoria del sistema. Las transferencias de acceso directo a memoria (DMA) del controlador host al programador pueden impedir que los procesadores del sistema entren en estados de suspensión más profundos, como C3.

La suspensión selectiva está habilitada de forma predeterminada. Microsoft recomienda encarecidamente no deshabilitar la suspensión selectiva.

Los controladores de cliente no deben intentar determinar si la suspensión selectiva está habilitada antes de enviar solicitudes inactivas. Deben enviar solicitudes inactivas siempre que el dispositivo esté inactivo. Si se produce un error en la solicitud inactiva, el controlador cliente debe restablecer el temporizador de inactividad y volver a intentarlo.

Para suspender de forma selectiva un dispositivo USB, existen dos mecanismos diferentes: IRP de solicitud inactiva (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) y establecer IRP de energía (IRP_MN_SET_POWER). El mecanismo que se va a usar depende del tipo de dispositivo: compuesto o no compuesto.

Selección de un mecanismo de suspensión selectiva

Los controladores de cliente para una interfaz en un dispositivo compuesto que habilitan la interfaz para la reactivación remota con un IRP de reactivación de espera (IRP_MN_WAIT_WAKE), deben usar el mecanismo IRP de solicitud inactiva (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) para suspender selectivamente un dispositivo.

Para obtener información sobre la reactivación remota, consulte:

En esta sección se explica el mecanismo de suspensión selectiva de Windows.

Envío de una solicitud de inactividad USB IRP

Cuando un dispositivo se pone en modo inactivo, el controlador cliente informa al controlador de bus mediante el envío de una solicitud de inactividad IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). Una vez que el controlador de bus determina que es seguro colocar el dispositivo en un estado de baja potencia, llama a la rutina de devolución de llamada que el controlador de dispositivo cliente ha pasado la pila con el IRP de solicitud inactiva.

En la rutina de devolución de llamada, el controlador cliente debe cancelar todas las operaciones de E/S pendientes y esperar a que se completen todos los IRP de E/S USB. A continuación, puede emitir una solicitud de IRP_MN_SET_POWER para cambiar el estado de alimentación del dispositivo WDM a D2. La rutina de retorno de llamada debe esperar a que se complete la solicitud D2 antes de regresar. Para obtener más información sobre la rutina de devolución de llamada de notificación inactiva, consulte Implementación de una rutina de devolución de llamada IRP de solicitud inactiva USB.

El conductor del autobús no completa la solicitud de inactividad IRP después de llamar a la rutina de devolución de llamada de la notificación de inactividad. En su lugar, el conductor de autobús mantiene la solicitud inactiva IRP pendiente hasta que se cumple una de las condiciones siguientes:

  • Se recibe un IRP_MN_SURPRISE_REMOVAL o un IRP_MN_REMOVE_DEVICE IRP. Cuando se recibe uno de estos IRP, el IRP de solicitud inactiva se completa con STATUS_CANCELLED.
  • El controlador de bus recibe una solicitud para colocar el dispositivo en un estado de energía de trabajo (D0). Al recibir esta solicitud, el controlador del bus completa el IRP de la petición inactiva pendiente con STATUS_SUCCESS.

Las restricciones siguientes se aplican al uso de IRP de solicitud inactiva:

  • Los controladores deben estar en el estado de energía del dispositivo D0 cuando envían una IRP de solicitud de inactividad.
  • Los controladores deben enviar solo una solicitud inactiva IRP por pila de dispositivos.

En el siguiente código de ejemplo de WDM se ilustran los pasos que lleva un controlador de dispositivo para enviar un IRP de solicitud USB inactiva. La comprobación de errores se omite en el ejemplo de código siguiente.

  1. Asigne e inicialice el IRP IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION

    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. Asigne e inicialice la estructura de información de solicitudes inactivas (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. Establezca una rutina de finalización.

    El controlador cliente debe asociar una rutina de finalización con el IRP de solicitud inactiva. Para obtener más información sobre la rutina de finalización de notificación inactiva y el código de ejemplo, consulte Implementar una rutina de finalización de IRP de solicitud inactiva USB.

    IoSetCompletionRoutine (irp,
        IdleNotificationRequestComplete,
        DeviceContext,
        TRUE,
        TRUE,
        TRUE);
    
  4. Almacene la solicitud inactiva en la extensión del dispositivo.

    deviceExtension->PendingIdleIrp = irp;
    
  5. Envíe la solicitud de reposo al controlador primario.

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

Cancelación de una solicitud de inactividad USB

En determinadas circunstancias, es posible que un controlador de dispositivo tenga que cancelar una solicitud inactiva IRP enviada al controlador de autobús. Esta situación puede producirse si el dispositivo se quita, se activa después de estar inactivo y al enviar la solicitud debido a la inactividad, o si todo el sistema está pasando a un estado de energía más bajo.

El controlador cliente cancela el IRP inactivo llamando a IoCancelIrp. En la tabla siguiente se describen tres escenarios para cancelar un IRP inactivo y se especifica la acción que debe realizar el controlador:

Escenario Mecanismo de cancelación de solicitudes inactivas
El controlador cliente cancela el IRP inactivo y no se ha invocado a la rutina de devolución de llamada de notificación inactiva USB. La pila del controlador USB completa el IRP inactivo. Dado que el dispositivo nunca abandonó el D0, el controlador no cambia el estado del dispositivo.
El controlador cliente cancela el IRP inactivo, la pila de controladores USB llama a la rutina de devolución de llamada de notificación de inactividad de USB, y esta aún no ha finalizado. Es posible que se invoque la rutina de notificación inactiva USB aunque el controlador cliente haya cancelado la solicitud IRP. En este caso, la rutina de devolución de llamada del controlador cliente todavía debe apagar el dispositivo al cambiarlo sincrónicamente a un estado de energía inferior.

Cuando el dispositivo está en estado de menor potencia, el controlador cliente puede enviar una solicitud D0 .

Como alternativa, el controlador puede esperar a que la pila de controladores USB complete el IRP en estado de reposo y, a continuación, envíe el IRP D0.

Si la rutina de devolución de llamada no puede colocar el dispositivo en un estado de baja potencia debido a una memoria insuficiente para asignar un IRP de energía, debe cancelar el IRP inactivo y salir inmediatamente. El IRP inactivo no se completa hasta que se devuelve la rutina de devolución de llamada. Por lo tanto, la rutina de devolución de llamada no debe bloquear la espera de que se complete el IRP inactivo cancelado.
El dispositivo ya está en estado de baja potencia. Si el dispositivo ya está en un estado de baja potencia, el controlador cliente puede enviar un IRP D0 . La pila del controlador USB completa el IRP de solicitud inactiva con STATUS_SUCCESS.

Como alternativa, el controlador puede cancelar el IRP inactivo, esperar a que la pila del controlador USB complete el IRP inactivo y, a continuación, enviar un IRP D0 .

Rutina de finalización de IRP de solicitud inactiva USB

En muchos casos, un conductor de autobús podría requerir la ejecución de la rutina de finalización del IRP de solicitud inactiva de un conductor. Si se produce esta situación, un controlador cliente debe detectar por qué el controlador de bus completó el IRP. El código de estado devuelto puede proporcionar esta información. Si el código de estado no es STATUS_POWER_STATE_INVALID, el controlador debe colocar su dispositivo en D0 si el dispositivo aún no está en D0. Si el dispositivo sigue inactivo, el controlador puede enviar otro IRP de solicitud de inactividad.

Nota:

La rutina de finalización de IRP de solicitud inactiva no debe bloquear la espera de que se complete una solicitud de energía D0 . El controlador central puede llamar a la rutina de finalización en el contexto de un IRP de energía, y el bloqueo en otro IRP de energía en la rutina de finalización puede provocar un interbloqueo.

En la lista siguiente se indica cómo una rutina de finalización de una solicitud inactiva debe interpretar algunos códigos de estado comunes:

Código de estado Descripción
ESTADO_ÉXITO Indica que el dispositivo ya no debe suspenderse. Sin embargo, los usuarios deben asegurar que sus dispositivos están encendidos y ponerlos en D0 si aún no están en D0.
ESTADO_CANCELADO El conductor del autobús completa la solicitud IRP inactiva con STATUS_CANCELLED en cualquiera de las circunstancias siguientes:
  • El controlador de dispositivo canceló el IRP.
  • Se requiere un cambio de estado de energía del sistema.
STATUS_POWER_STATE_INVALID Indica que el controlador de dispositivo solicitó un estado de alimentación D3 para su dispositivo. Cuando se produce esta solicitud, el controlador de bus completa todos los IRP inactivos pendientes con STATUS_POWER_STATE_INVALID.
ESTADO_DISPOSITIVO_OCUPADO Indica que el controlador de bus ya contiene una solicitud inactiva IRP pendiente para el dispositivo. Solo un IRP inactivo puede estar pendiente a la vez para un dispositivo determinado. El envío de múltiples IRPs de solicitud inactiva es un error por parte del propietario de la política de energía. El sistema de escritura del controlador soluciona el error.

En el ejemplo de código siguiente se muestra una implementación de ejemplo para la rutina de finalización de solicitudes inactivas.

/*
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;
}

Rutina de devolución de llamada de notificación inactiva USB

El controlador de bus (ya sea una instancia del controlador central o el controlador primario genérico) determina cuándo es seguro suspender los elementos secundarios de su dispositivo. Si es así, llama a la rutina de notificación de inactividad proporcionada por el controlador cliente de cada hijo.

El prototipo de función para USB_IDLE_CALLBACK es el siguiente:

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

Un controlador de dispositivo debe realizar las siguientes acciones en su rutina de devolución de llamada de notificación inactiva:

  • Solicite una IRP_MN_WAIT_WAKE IRP para el dispositivo si el dispositivo debe estar habilitado para la reactivación remota.
  • Cancele todas las E/S y prepare el dispositivo para ir a un estado de energía inferior.
  • Coloque el dispositivo en un estado de suspensión de WDM llamando a PoRequestPowerIrp con el parámetro PowerState establecido en el valor del enumerador PowerDeviceD2 (definido en wdm.h; ntddk.h).

Tanto el controlador del concentrador como el controlador primario genérico USB (Usbccgp.sys) llaman a la rutina de devolución de llamada por notificación de inactividad en IRQL = PASSIVE_LEVEL. La rutina de devolución de llamada puede bloquearse mientras espera a que se complete la solicitud de cambio de estado de energía.

La rutina de devolución de llamada solo se invoca mientras el sistema está en S0 y el dispositivo está en D0.

Las restricciones siguientes se aplican a las rutinas de devolución de llamada para notificación de solicitudes inactivas:

  • Los controladores de dispositivo pueden iniciar una transición de estado de energía del dispositivo de D0 a D2 en la rutina de devolución de llamada de notificación inactiva, pero no se permite ninguna otra transición de estado de energía. En concreto, un controlador no debe intentar cambiar su dispositivo a D0 mientras ejecuta su rutina de devolución de llamada.
  • Los controladores de dispositivos no deben solicitar más de un IRP de energía desde la rutina de callback de notificación de inactividad.

Armando dispositivos para el activación en la rutina de llamada de retorno de notificación inactiva

La rutina de devolución de llamada de notificación inactiva debe determinar si su dispositivo tiene una solicitud IRP_MN_WAIT_WAKE pendiente. Si no hay ninguna solicitud IRP_MN_WAIT_WAKE pendiente, la rutina de devolución de llamada debe enviar una solicitud IRP_MN_WAIT_WAKE antes de poner en suspensión el dispositivo. Para obtener más información sobre el mecanismo de reactivación de espera, consulte Compatibilidad con dispositivos que tienen funcionalidades de reactivación.

Suspensión global de USB

La especificación USB 2.0 define la suspensión global como la suspensión de todo el bus detrás de un controlador de host USB cesando todo el tráfico USB en el bus, incluidos los paquetes de comienzo de trama. Los dispositivos de bajada que aún no están suspendidos detectan el estado Inactivo en su puerto ascendente y entran en el estado de suspensión por su cuenta. Windows no implementa la suspensión global de esta manera. Windows siempre suspende de forma selectiva cada dispositivo USB a través de un controlador de host USB antes de que cese todo el tráfico USB en el bus.

Condiciones de suspensión global

El controlador del concentrador USB suspende selectivamente cualquier concentrador donde todos sus dispositivos conectados estén en estado de alimentación de dispositivo D1, D2 o D3 . El bus entra en modo de suspensión global una vez que todos los concentradores USB se suspenden de forma selectiva. La pila del controlador USB trata un dispositivo como Inactivo siempre que el dispositivo esté en un estado de dispositivo WDM de D1, D2 o D3.