/* * upstream_statemachine.c * * Created on: 20/08/2015 * Author: Robert Fisk * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "upstream_statemachine.h" #include "upstream_spi.h" #include "upstream_hid.h" #include "usb_device.h" #include "usbd_core.h" #include "usbd_msc.h" #include "usbd_hid.h" #include "build_config.h" #define POLL_DEVICE_CONNECTED_TIMEOUT_MS 1000 UpstreamStateTypeDef UpstreamState = STATE_TEST_INTERFACE; InterfaceCommandClassTypeDef ConfiguredDeviceClass = COMMAND_CLASS_INTERFACE; uint32_t PollDeviceConnectedTimer; void Upstream_StateMachine_TestInterfaceReplyCallback(UpstreamPacketTypeDef* replyPacket); void Upstream_StateMachine_NotifyDevice(UpstreamPacketTypeDef* freePacket); void Upstream_StateMachine_NotifyDeviceReplyCallback(UpstreamPacketTypeDef* replyPacket); void Upstream_StateMachine_PollDeviceConnectedReplyCallback(UpstreamPacketTypeDef* replyPacket); void Upstream_InitStateMachine(void) { UpstreamPacketTypeDef* freePacket; uint16_t i; uint8_t testDataValue; Upstream_InitSPI(); //Prepare SPI test packet freePacket = Upstream_GetFreePacketImmediately(); if (freePacket == NULL) { UpstreamState = STATE_ERROR; return; } freePacket->Length16 = UPSTREAM_PACKET_LEN_16; freePacket->CommandClass = COMMAND_CLASS_INTERFACE; freePacket->Command = COMMAND_INTERFACE_ECHO; //Fill our test packet with some junk testDataValue = 0xFF; for (i = 0; i < MSC_MEDIA_PACKET; i++) { freePacket->Data[i] = testDataValue; testDataValue += 39; } if (Upstream_TransmitPacket(freePacket) == HAL_OK) { Upstream_ReceivePacket(Upstream_StateMachine_TestInterfaceReplyCallback); } } //Used by upstream_spi freakout macro, indicates we should stop everything. void Upstream_StateMachine_SetErrorState(void) { UpstreamState = STATE_ERROR; if ((ConfiguredDeviceClass > COMMAND_CLASS_INTERFACE) && (ConfiguredDeviceClass < COMMAND_CLASS_ERROR)) { USBD_DeInit(&hUsbDeviceFS); } } InterfaceCommandClassTypeDef Upstream_StateMachine_CheckActiveClass(void) { if (UpstreamState == STATE_ERROR) { return COMMAND_CLASS_ERROR; } if ((UpstreamState == STATE_DEVICE_ACTIVE) || (UpstreamState == STATE_SUSPENDED)) { return ConfiguredDeviceClass; } //else: UPSTREAM_STATEMACHINE_FREAKOUT; return COMMAND_CLASS_INTERFACE; } uint32_t Upstream_StateMachine_GetSuspendState(void) { if (UpstreamState == STATE_SUSPENDED) { return 1; } return 0; } void Upstream_StateMachine_TestInterfaceReplyCallback(UpstreamPacketTypeDef* replyPacket) { uint16_t i; uint8_t testDataValue; if (UpstreamState >= STATE_ERROR) { return; } if ((UpstreamState != STATE_TEST_INTERFACE) || (replyPacket == NULL)) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } if (replyPacket->Length16 != UPSTREAM_PACKET_LEN_16) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } testDataValue = 0xFF; for (i = 0; i < MSC_MEDIA_PACKET; i++) { if (replyPacket->Data[i] != testDataValue) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } testDataValue += 39; } //SPI interface passed checks. Now we wait for a device to be attached to downstream. Upstream_StateMachine_NotifyDevice(replyPacket); } void Upstream_StateMachine_NotifyDevice(UpstreamPacketTypeDef* freePacket) { UpstreamState = STATE_WAIT_DEVICE; freePacket->Length16 = UPSTREAM_PACKET_HEADER_LEN_16; freePacket->CommandClass = COMMAND_CLASS_INTERFACE; freePacket->Command = COMMAND_INTERFACE_NOTIFY_DEVICE; if (Upstream_TransmitPacket(freePacket) == HAL_OK) { Upstream_ReceivePacket(Upstream_StateMachine_NotifyDeviceReplyCallback); } } void Upstream_StateMachine_NotifyDeviceReplyCallback(UpstreamPacketTypeDef* replyPacket) { InterfaceCommandClassTypeDef newActiveClass = COMMAND_CLASS_INTERFACE; USBD_ClassTypeDef* newClassPointer; if (UpstreamState >= STATE_ERROR) { return; } if ((UpstreamState != STATE_WAIT_DEVICE) || (replyPacket == NULL)) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } if (replyPacket->Length16 != (UPSTREAM_PACKET_HEADER_LEN_16 + 1)) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } switch (replyPacket->Data[0]) { #ifdef CONFIG_MASS_STORAGE_ENABLED case COMMAND_CLASS_MASS_STORAGE: newActiveClass = COMMAND_CLASS_MASS_STORAGE; newClassPointer = &USBD_MSC; break; #endif #ifdef CONFIG_MOUSE_ENABLED case COMMAND_CLASS_HID_MOUSE: newActiveClass = COMMAND_CLASS_HID_MOUSE; newClassPointer = &USBD_HID; USBD_HID_PreinitMouse(); break; #endif #ifdef CONFIG_KEYBOARD_ENABLED case COMMAND_CLASS_HID_KEYBOARD: newActiveClass = COMMAND_CLASS_HID_KEYBOARD; newClassPointer = &USBD_HID; USBD_HID_PreinitKeyboard(); break; #endif //Add other supported classes here... } Upstream_ReleasePacket(replyPacket); if (newActiveClass == COMMAND_CLASS_INTERFACE) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } //Downstream should never change the active device class without rebooting! if ((ConfiguredDeviceClass != COMMAND_CLASS_INTERFACE) && (ConfiguredDeviceClass != newActiveClass)) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } UpstreamState = STATE_DEVICE_ACTIVE; ConfiguredDeviceClass = newActiveClass; USB_Device_Init(); USBD_RegisterClass(&hUsbDeviceFS, newClassPointer); USBD_Start(&hUsbDeviceFS); //The USB device stack will now receive commands from our host. //All we need to do is monitor for downstream device disconnection. } void Upstream_StateMachine_DeviceDisconnected(void) { if ((ConfiguredDeviceClass == COMMAND_CLASS_INTERFACE) || (ConfiguredDeviceClass >= COMMAND_CLASS_ERROR)) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } USBD_DeInit(&hUsbDeviceFS); Upstream_GetFreePacket(Upstream_StateMachine_NotifyDevice); } //Suspend event activated by our host void Upstream_StateMachine_Suspend(void) { if (UpstreamState >= STATE_SUSPENDED) //Ignore when already suspended, or in error state { return; } if (UpstreamState != STATE_DEVICE_ACTIVE) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } PollDeviceConnectedTimer = 0; UpstreamState = STATE_SUSPENDED; } //Resume event activated by our host void Upstream_StateMachine_CheckResume(void) { if (UpstreamState == STATE_SUSPENDED) { UpstreamState = STATE_DEVICE_ACTIVE; } } //Activated by downstream report, causing us to wake up the host void Upstream_StateMachine_Wakeup(void) { USBD_ClassTypeDef* activeClass; if (UpstreamState != STATE_SUSPENDED) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } //This is how I'd wakeup the host, IF IT ACTUALLY WORKED! //USBD_LL_WakeupHost(&hUsbDeviceFS); //This is really ugly! But wakeup seems to be broken on the STM32, so we do it the hard way. activeClass = USBD_DeInit(&hUsbDeviceFS); USB_Device_Init(); USBD_RegisterClass(&hUsbDeviceFS, activeClass); USBD_Start(&hUsbDeviceFS); } //Called by Systick_Handler every 1ms, at high interrupt priority. void Upstream_StateMachine_PollDeviceConnected(void) { UpstreamPacketTypeDef* freePacket; if ((UpstreamState != STATE_SUSPENDED) || (ConfiguredDeviceClass != COMMAND_CLASS_MASS_STORAGE)) { return; } if (++PollDeviceConnectedTimer >= POLL_DEVICE_CONNECTED_TIMEOUT_MS) { PollDeviceConnectedTimer = 0; freePacket = Upstream_GetFreePacketImmediately(); if (freePacket == NULL) { UpstreamState = STATE_ERROR; return; } freePacket->Length16 = UPSTREAM_PACKET_HEADER_LEN_16; freePacket->CommandClass = COMMAND_CLASS_MASS_STORAGE; freePacket->Command = COMMAND_MSC_POLL_DISCONNECT; if (Upstream_TransmitPacket(freePacket) == HAL_OK) { Upstream_ReceivePacket(Upstream_StateMachine_PollDeviceConnectedReplyCallback); } } } void Upstream_StateMachine_PollDeviceConnectedReplyCallback(UpstreamPacketTypeDef* replyPacket) { if ((UpstreamState != STATE_SUSPENDED) || (replyPacket == NULL)) { UPSTREAM_STATEMACHINE_FREAKOUT; return; } //Downstream device is still connected, so nothing to do here Upstream_ReleasePacket(replyPacket); }