AVR all-firmware USB and PC boot/BIOS

Go To Last Post
7 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Anyone know...
USB has a bootstrap profile mode, intended for keyboard/mouse for a simple BIOS routine during booting and in the BIOS setup program.

Do contemporary PC BIOSes not need that mode, i.e., can use the same USB profile that a Windows HID uses?

Goal: Microprocessor with USB that can send a string upon pushing a button on the microprocessor, and that string will work with either the BIOS or with Windows' HID drivers.

BIOS and Windows must detect device.

I suspect that BIOSes of the last 2 years or so don't need the special boot profile? Yes?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> I suspect that BIOSes of the last 2 years or so don't need the special boot profile?

- You can apply common "standard" report descriptor (or report format) both for BIOS and Windows (or any other OS).

- In this case also, your firmware has to support Set_Protocol and Get_Protocol requests, though it does nothing other than holding/reporting the current protocol status.

In the Set_Configuration handler (ie. at the end of enumeration), keyboard and mouse are set to "Report" protocol (normal status). Set_Protocol request switches it to "Boot" protocol.

Just after PC power on, BIOS enumerates attached USB devices. BIOS puts Set_Protocol to switch mouse and keyboard into "Boot" protocol.

At the start up of Windows after BIOS, USB devices get enumerated again. With this enumeration, mouse and keyboard return to "Report" protocol. As Windows don't put any Set_Protocol, these devices are kept in "Report" protocol.

For the details, see these sections of the HID spec,
http://www.usb.org/developers/de...

- Appendix B: Boot Interface Descriptors (p59)
- F.3 Boot Keyboard Requirements (p74)
- F.5 Keyboard: Using the Keyboard Boot Protocol (p75)

Tsuneo

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I half understand that.

Being experienced but lazy, is there an implementation of the "Boot" protocol for an AVR (or other) small micro, combined with a Report protocol, so the micro can react to either BIOS or Windows?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't know any "complete" example for boot keyboard/mouse on the web, not just for AVR.
But most of keyboard/mouse examples apply the "standard" report descriptor, which is shown in the HID spec. Starting with one of such an example, you'll be able to make boot keyboard/mouse.

Here, I pick up the keyboard example on LUFA, and show you the way to make it boot one.
http://www.fourwalledcubicle.com...

The implementation of boot protocol on LUFA is nearly perfect. Minor bug fixes make it complete.

1) Interface descriptor - Interface triad
LUFA example has already set the triad to the boot one

LUFA 090810\Demos\Device\ClassDriver\Keyboard\Descriptors.c

USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor =
{
    ...
    .Interface = 
    {
        ...             
        .Class        = 0x03,
        .SubClass     = 0x01,  // Boot Interface Subclass = 1
        .Protocol     = 0x01,  // Boot Keyboard = 1, Boot mouse = 2
        ...

2) Get_Protocol / Set_Protocol requests support
As the common report format is applied to boot/report protocol, there is nothing other than the maintenance of the protocol status variable.

LUFA holds the protocol status in UsingReportProtocol variable.
This variable is initialized correctly in Set_Configuration handler.

LUFA 090810\LUFA\Drivers\USB\Class\Device\HID.c

bool HID_Device_ConfigureEndpoints(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo)
{
    memset(&HIDInterfaceInfo->State, 0x00, sizeof(HIDInterfaceInfo->State));
    HIDInterfaceInfo->State.UsingReportProtocol = true;

The protocl status is changed by Set_Protocol request, and sent back to host by Get_Protocol request.
LUFA handles Set_Protocol / Get_Protocol requests properly, too.

LUFA 090810\LUFA\Drivers\USB\Class\Device\HID.c

void HID_Device_ProcessControlRequest(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo)
{
    ...
        case REQ_GetProtocol:
            if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
            {
                Endpoint_ClearSETUP();

                Endpoint_Write_Byte(HIDInterfaceInfo->State.UsingReportProtocol);
                Endpoint_ClearIN();

                Endpoint_ClearStatusStage();
            }
            
            break;
        case REQ_SetProtocol:
            if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
            {
                Endpoint_ClearSETUP();

                HIDInterfaceInfo->State.UsingReportProtocol = (USB_ControlRequest.wValue != 0x0000);
                
                Endpoint_ClearStatusStage();
            }
            
            break;

3) Set_Report(Output) support
The on/off state of Keyboard LEDs is set by Set_Report( Output ) request by BIOS.
LUFA has almost fine implementation of Set_Report handler, but it's better to check the report type, as follows.

LUFA 090810\LUFA\Drivers\USB\Class\Device\HID.c

enum HID_REPORTTYPE {
    HID_REPORTTYPE_INPUT   = 0,
    HID_REPORTTYPE_OUTPUT  = 1,
    HID_REPORTTYPE_FEATURE = 2
};

void HID_Device_ProcessControlRequest(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo)
{
    if (!(Endpoint_IsSETUPReceived()))
      return;
      
    if ((USB_ControlRequest.wIndex   != HIDInterfaceInfo->Config.InterfaceNumber) ) // &&       // <----- modify this line
//      (USB_ControlRequest.bRequest != REQ_SetIdle))                                           // <----- delete this line
                                                                                                // Set_Idle should apply to each HID interface independently
    {
        return;
    }

    switch (USB_ControlRequest.bRequest)
    {
    ...
        case REQ_SetReport:
            if ( (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
             && ((USB_ControlRequest.wValue & 0xFF00) == ((HID_REPORTTYPE_OUTPUT) << 8)) )       // <----- this line was added
            {
                Endpoint_ClearSETUP();
                
                uint16_t ReportOUTSize = USB_ControlRequest.wLength;
                uint8_t  ReportOUTData[ReportOUTSize];
                uint8_t  ReportID = (USB_ControlRequest.wValue & 0xFF);

                Endpoint_Read_Control_Stream_LE(ReportOUTData, ReportOUTSize);
                Endpoint_ClearIN();
                
                CALLBACK_HID_Device_ProcessHIDReport(HIDInterfaceInfo, ReportID, ReportOUTData, ReportOUTSize);
            }
            
            break;

4) Get_Idle/Set_Idle requests support
HID spec notes about the report duration of boot keyboard, as follows.

Quote:
F.3 Boot Keyboard Requirements (HID1_11.pdf p74)
- The Boot Keyboard shall send data reports when the interrupt in pipe is polled, even when there are no new key events. The Set_Idle request shall override this behavior as described in the HID Class specification.

LUFA holds the duration in IdleCount variable.
Get_Idle and Set_Idle handlers properly manupilate this variable.

LUFA 090810\LUFA\Drivers\USB\Class\Device\HID.c

void HID_Device_ProcessControlRequest(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo)
{
    ...
        case REQ_SetIdle:
            if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
            {
                if (  // (USB_ControlRequest.wIndex         == HIDInterfaceInfo->Config.InterfaceNumber) &&  // <--- duplicated, already checked at the top of this function
                    (USB_ControlRequest.wValue & 0xFF) == 0)
                {
                    Endpoint_ClearSETUP();
                    
                    HIDInterfaceInfo->State.IdleCount = ((USB_ControlRequest.wValue & 0xFF00) >> 6);
                    
                    Endpoint_ClearStatusStage();
                }
            }
            
            break;
        case REQ_GetIdle:
            if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
            {       
                Endpoint_ClearSETUP();
                
                Endpoint_Write_Byte(HIDInterfaceInfo->State.IdleCount >> 2);
                Endpoint_ClearIN();

                Endpoint_ClearStatusStage();
            }

            break;

Keyboard and mouse are often implemented using low-speed device.
Considering this fact, 10 ms is better for the bInterval field of the endpoint descriptor. 10 ms is the least interval allowed for low-speed device.
Usually, host reduces this value to 8 ms, because just the interval of power of 2 (1, 2, 4, 8, 16, 32...) is supported by the host controller.

LUFA 090810\Demos\Device\ClassDriver\Keyboard\Descriptors.c

USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor =
{
    ...
    .KeyboardEndpoint = 
        {
            .Header                 = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},

            .EndpointAddress        = (ENDPOINT_DESCRIPTOR_DIR_IN | KEYBOARD_EPNUM),
            .Attributes             = EP_TYPE_INTERRUPT,
            .EndpointSize           = KEYBOARD_EPSIZE,
            .PollingIntervalMS      = 0x0A                        // <----- set to 10 ms
        },

In the enumeration, the IdleCount is initialized to 8 ms.

LUFA 090810\LUFA\Drivers\USB\Class\Device\HID.c
bool HID_Device_ConfigureEndpoints(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo)
{
    memset(&HIDInterfaceInfo->State, 0x00, sizeof(HIDInterfaceInfo->State));
    HIDInterfaceInfo->State.UsingReportProtocol = true;
    HIDInterfaceInfo->State.IdleCount = 8;                        //  <----- set to 8 ms

The rest is shown in LUFA 090810\Demos\Device\LowLevel\Keyboard\Keyboard.c
Keyboard switches are scanned in IdleCount interval - HID_Task().
And then, the input report which shows the current status of swwitches is made - CreateKeyboardReport()
If the interrupt endpoint is empty, the input report is sent to this endpoint, else the report is discarded.

One suggestion,
LUFA applies an on-chip timer to count the idle duration. Instead, SOF interrupt is handy as a 1ms timer.

Tsuneo

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Tsuneo,

Thanks for the overview on LUFA - I'll fix up those mistakes you've mentioned.

I've made LUFA use a hardware timer in the user app to keep track of the idle period, because otherwise there's no way of knowing how the user has hooked the SOF interrupt. However, for the sake of simplicity, I'll probably remove it from the next release and use the SOF interrupt, telling the user not to hook it.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Dean,

May I send you congratulation on your success?
It's my shame, that I've not been aware of this excellent USB stack is there on the web.
As LUFA is almost perfect, such trivial matters like above post are left for me to point out.

As of the hardware timer,
The reason why I recommend you SOF timing, is synchronization to the bus clock.
On-chip timer is driven by the local crystal OSC, which isn't synchronized to the bus clock. In long period, phase jitter occurs between the timer and the bus timing. The timing for the key scan and report has to be synchronized to the bus clock, to put report for each IN transaction on the interrupt IN EP. On the other hand, ADC sampling is better to be driven by the local OSC, to guarantee the accuracy of the sampling rate. The tolerance of bus clock is +/-0.25% at most for full speed.
Depending on the target, we choose either bus clock (SOF) or local OSC.

If any major change would be expected for LUFA 090810, it's the control transfer handling.
To simplify the code, you have decided to wait transactions for DATA and STATUS stages in the local loop on Template_Endpoint_Control_R/W.c. Consider to open the local loop to the superloop, or RTOS-like interrupt-based scheduling. For CDC and audio, for example, control transfers (class-specific requests) are used even after enumeration. With the current implementation, such requests would disturb the performance of the major task.

Tsuneo

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

May I send you congratulation on your success?
It's my shame, that I've not been aware of this excellent USB stack is there on the web.
As LUFA is almost perfect, such trivial matters like above post are left for me to point out.

Cheers! Don't be afraid to point out the bits I've missed - they help me improve the code so that others can benefit.

Quote:

The reason why I recommend you SOF timing, is synchronization to the bus clock.
On-chip timer is driven by the local crystal OSC, which isn't synchronized to the bus clock. In long period, phase jitter occurs between the timer and the bus timing. The timing for the key scan and report has to be synchronized to the bus clock, to put report for each IN transaction on the interrupt IN EP.

I do understand why using the hardware SOFs to count milliseconds for USB related tasks is a good idea - however, I'm not sure how best to integrate it into the library. Having a user-callback every millisecond seems wasteful for those who don't want it, but I think I'll just add in special runtime enable/disable commands.

Quote:

If any major change would be expected for LUFA 090810, it's the control transfer handling.

I'm aware that the current control handling isn't perfect, but I'm going to fix it up in the next release or so once I've completed the host mode class drivers and the other 9,000 things on my TODO list ;).

If you have any other suggestions, please don't hesitate to email me them!

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!