-
-
Notifications
You must be signed in to change notification settings - Fork 2
07. More Control Packets
The device does not yet enumerate correctly or work. But before we take care of the descriptor, first we'll add all of the missing Handlers.
We need to support two more interrupts: the suspend and wakeup-events, which put the device in a low power mode when the bus is inactive.
We will distinguish, whether a specific request is valid at this point of the configuration stage or not, as the device is required to react differently according to the spec. A complete explanation for all the control messages and their expected behaviour can be taken from the book "USB Complete" or the USB-Spec.
We will split the handling of those control events into device, interface and endpoint events, based on the bitmask of the RequestType. We will also introduce the device state (Default after Reset, Address after setting the Address, Configured after selecting the active configuration) to track which requests should STALL and which shouldn't.
Device Requests:
- Get Status
The host expects two data packages of which only the first two bits are used for FullSpeed. Bit0 denotes whether the device is self-powered and Bit1 if the device has RemoteWakeup enabled. The last bit should be zero by default and will be set by the host. - Clear Feature / Set Feature
For FullSpeed, this just enables or disables the remote-wakeup feature if supported. - Set Address / Get Descriptor
Was already handled - Set Descriptor
Allows the host to set a custom descriptor, which is generally not supported on any device - Get Configuration / Set Configuration
Allows the host to select a configuration. Configurations are 1-based while 0 means that no configuration was selected yet. Setting a configuration resets the Data-toggle to DATA0 on all endpoints except the control endpoint.
Interface Requests:
- Get Status / Set Feature / Clear Feature
Interfaces have no status or feature and should return 0x0000 - Get Interface / Set Interface
Interfaces can have multiple variants (denoted through the AlternateID), which can be selected with this command.
Endpoint Requests:
- Get Status / Clear Feature / Set Feature
Every endpoint has to be Halt-able. This indicates that the endpoint has an error (returns a STALL) and needs to be reset by the driver, which will be done with the Feature-Requests. - Synch Frame
This request is used for synchronized endpoints
First we add some callbacks into the usb_config, so that user code can do the necessary actions to minimize power consumption.
__weak void USB_SuspendDevice() {
}
__weak void USB_WakeupDevice(){
}
Then we enable the Suspend- and WakeUp-Interrupts, as we are expected to handle them accordingly and extend the ISR.
void USB_Init() {
...
// Enable all interrupts & the internal pullup to put 1.5K on D+ for FullSpeed USB
USB->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_WKUPM | USB_CNTR_SUSPM;
USB->BCDR |= USB_BCDR_DPPU;
...
}
void USB_LP_IRQHandler() {
if ((USB->ISTR & USB_ISTR_RESET) != 0) {
...
} else if ((USB->ISTR & USB_ISTR_CTR) != 0) {
...
} else if((USB->ISTR & USB_ISTR_SUSP) != 0){
USB->ISTR = ~USB_ISTR_SUSP;
USB_SuspendDevice();
// On Suspend, the device should enter low power mode and turn off the USB-Peripheral
USB->CNTR |= USB_CNTR_FSUSP;
// If the device still needs power from the USB Host
USB->CNTR |= USB_CNTR_LPMODE;
} else if((USB->ISTR & USB_CLR_WKUP) != 0) {
USB->ISTR = ~USB_ISTR_WKUP;
// Resume peripheral
USB->CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE);
USB_WakeupDevice();
}
}
First we need to configure if the device is self-powered and how many endpoints we use for storing the halted-state in the usb_config
#define USB_SelfPowered 0
#define USB_NumInterfaces 1
#define USB_NumEndpoints 2
Now we can implement the necessary variables used for storing the state
static char ActiveConfiguration = 0x00;
static char DeviceState = 0x00; // 0 - Default, 1 - Address, 2 - Configured
static char EndpointState[USB_NumEndpoints] = {0};
Now the missing device requests. For the default responses (STALL vs VALID) check USB Complete. We only support one configuration.
if ((setup->RequestType & 0x0F) == 0) { // Device Requests
switch (setup->Request) {
case 0x00: // Get Status
EP0_Buf[1][0] = USB_SelfPowered;
EP0_Buf[1][1] = 0x00;
BTable[0].COUNT_TX = 2;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
break;
case 0x01: // Clear Feature
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
break;
case 0x03: // Set Feature
BTable[0].COUNT_TX = 0;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
break;
case 0x05: // Set Address
...
break;
case 0x06: // Get Descriptor
...
break;
case 0x07: // Set Descriptor
// Allows the Host to alter the descriptor. Not supported
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
break;
case 0x08: // Get Configuration
if (DeviceState == 1 || DeviceState == 2) {
if (DeviceState == 1) {
ActiveConfiguration = 0;
}
EP0_Buf[1][0] = ActiveConfiguration;
BTable[0].COUNT_TX = 1;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
case 0x09: // Set Configuration
if (DeviceState == 1 || DeviceState == 2) {
BTable[0].COUNT_TX = 0;
switch (setup->Value & 0xFF) {
case 0:
DeviceState = 1;
ActiveConfiguration = 0;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
break;
case 1:
DeviceState = 2;
ActiveConfiguration = 1;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
break;
default:
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
break;
}
if (DeviceState == 2) {
USB_SetEP(&USB->EP1R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
USB_SetEP(&USB->EP2R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
USB_SetEP(&USB->EP3R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
USB_SetEP(&USB->EP4R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
USB_SetEP(&USB->EP5R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
USB_SetEP(&USB->EP6R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
USB_SetEP(&USB->EP7R, 0x00, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
}
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
}
}
Now the interface requests. We don't support alternate Interfaces.
else if ((setup->RequestType & 0x0F) == 0x01) { // Interface requests
switch (setup->Request) {
case 0x00: // Get Status
if (DeviceState == 2) {
EP0_Buf[1][0] = 0x00;
EP0_Buf[1][1] = 0x00;
BTable[0].COUNT_TX = 2;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
case 0x01: // Clear Feature
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
break;
case 0x03: // Set Feature
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
break;
case 0x0A: // Get Interface
if (DeviceState == 2 && setup->Index < USB_NumInterfaces) {
EP0_Buf[1][0] = 0x00;
BTable[0].COUNT_TX = 1;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
case 0x0B: // Set Interface /!\ USB InANutshell is wrong here, it confuses the decimal value (11) with the hex one (0x0B)
if (DeviceState == 2 && setup->Index < USB_NumInterfaces) {
BTable[0].COUNT_TX = 0;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
USB_ResetClass(setup->Index, setup->Value);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
}
}
For the Set Interface
-Request, we need to add the function USB_ResetClass
to our usb_config
. Normally we can ignore this function, except we need it for NCM.
void USB_ResetClass(char interface, char alternateId) {
// do nothing
}
And lastly the endpoint requests.
else if ((setup->RequestType & 0x0F) == 0x02) { // Endpoint requests
switch (setup->Request) {
case 0x00: // Get Status
if ((DeviceState == 2 || (DeviceState == 1 && setup->Index == 0x00)) && setup->Index < USB_NumEndpoints) {
if (setup->Value == 0x00) {
EP0_Buf[1][0] = EndpointState[setup->Index];
EP0_Buf[1][1] = 0x00;
BTable[0].COUNT_TX = 2;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
case 0x01: // Clear Feature
if ((DeviceState == 2 || (DeviceState == 1 && setup->Index == 0x00)) && setup->Index < USB_NumEndpoints) {
if (setup->Value == 0x00) {
EndpointState[setup->Index] = 0;
BTable[0].COUNT_TX = 0;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
case 0x03: // Set Feature
if ((DeviceState == 2 || (DeviceState == 1 && setup->Index == 0x00)) && setup->Index < USB_NumEndpoints) {
if (setup->Value == 0x00) {
EndpointState[setup->Index] = 1;
BTable[0].COUNT_TX = 0;
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
} else {
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
}
break;
case 0x0C: // Sync Frame /!\ USB InANutshell is wrong here again, as it confuses the decimal value (12) with the hex one (0x0C)
USB_SetEP(&USB->EP0R, USB_EP_TX_STALL, USB_EP_TX_VALID);
break;
}
}
Now we have implemented all the control requests in a standard fashion.
Nothing to test here. It should just work.