#include "bl.h"
#include <string.h>
// PAGE_SIZE : Flash Page size
// Low and MEDIUM Density F103 devices have 1 KiB Flash page
// High Density F103 devices have 2 KiB Flash page
byte UploadState; // 0 = waiting, 1 = started, 2 = finished
// While 0, LED flashes on USB polling.
// While 1, LED is under control of flash procedure
// When 2, "jump to user code" command has been received
// When 4, main loop terminates
/* USB Descriptors */
#define W(x) (x)&0xFF,(x)>>8
static const byte hidReportDesc[] = {
0x06, W(0xFF00),// Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (0x01)
0xA1, 1, // Collection (Application)
0x15, 0, // Logical Minimum (0)
0x26, W(0xFF), // Logical Maximum (255)
0x75, 8, // Report Size (8 bit)
0x95, 1, // Report Count (1 Byte)
0x09, 1, // Usage (1)
0xB1, 2, // Feature (Var), write 1 = reset page counter, 2 = start application
0x09, 2, // Usage (2)
0x81, 2, // Input (Var) informs about success/failure of flash page write
0x09, 3, // Usage (3)
0x96, W(PAGE_SIZE), // Report Count: This size informs hid-flash 2.2.3 about flash page size
0x91, 2, // Output (Var) = data to flash
0xC0, // End Collection
};
static const byte usbDeviceDesc[] = {
0x12, // bLength
1, // bDescriptorType Device
W(0x0110), // bcdUSB 1.10
0, // bDeviceClass (Use class information in the Interface Descriptors)
0, // bDeviceSubClass
0, // bDeviceProtocol
64, // bMaxPacketSize0
W(0x1209), // idVendor 0x1209
W(0xBEBA), // idProduct 0xBEBA
W(0x0400), // bcdDevice 4.00
1, // iManufacturer
2, // iProduct
0, // iSerialNumber
1 // bNumConfigurations 1
};
static const byte usbConfigDesc[34] = {
9, // bLength
2, // bDescriptorType (Configuration)
W(sizeof usbConfigDesc),// wTotalLength 34
1, // bNumInterfaces 1
1, // bConfigurationValue
0, // iConfiguration (String Index)
0x80, // bmAttributes Bus Powered
50>>1, // bMaxPower 50 mA
9, // bLength
4, // bDescriptorType (Interface)
0, // bInterfaceNumber 0
0, // bAlternateSetting
1, // bNumEndpoints 1
3, // bInterfaceClass HID
0, // bInterfaceSubClass
0, // bInterfaceProtocol
0, // iInterface
9, // bLength
0x21, // bDescriptorType HID
W(0x0111), // bcdHID 1.11
0, // bCountryCode
1, // bNumDescriptors
0x22, // bDescriptorType[0] HID
W(sizeof hidReportDesc),// wDescriptorLength[0]
7, // bLength
5, // bDescriptorType Endpoint
0x81, // bEndpointAddress IN 1
0x03, // bmAttributes Interrupt
W(1), // wMaxPacketSize
10, // bInterval 10 ms
};
constexpr size_t length(const char16_t*s) {return *s ? 1+length(s+1) : 0;}
template<size_t N>struct strdesc_t{
byte a[2+N+N]{2+N+N,3}; // bLength, bDescriptorType (String)
operator const byte*() const {return a;}
constexpr void putc(size_t i, char16_t c) {a[2+i+i]=c&0xFF; a[3+i+i]=c>>8;}
static constexpr auto make(const char16_t*s) {
strdesc_t<N> r;
for(size_t i=0; i<N; i++) r.putc(i,s[i]);
return r;
}
};
#define strdesc(s) (strdesc_t<length(u##s)>::make(u##s))
/* USB String Descriptors */
static constexpr auto usbStringDesc0 = strdesc("\u0409"); // English (United States)
static constexpr auto usbStringDesc1 = strdesc("Henrik Haftmann");
static constexpr auto usbStringDesc2 = strdesc("STM32F HID Bootloader");
static constexpr volatile uint32_t* bufferAddress(unsigned btable_addr) {
return reinterpret_cast<volatile uint32_t*>(PMAAddr + btable_addr*2);
}
// The <src> argument is the first here to help place the return value in the same register
// <src> must be aligned! (divisable by 4)
static const volatile uint32_t*fromPMA(const volatile uint32_t*src,byte*dst,unsigned len) {
auto d=reinterpret_cast<uint16_t*>(dst); // <dst> should be aligned, however, STM32 allows unaligned access
auto l=len>>1;
if (l) do *d++=*src++; while (--l); // copy 2 bytes
if (len&1) *reinterpret_cast<uint8_t*>(d)=*src; // copy low byte - the return value cannot be used for continuing!
return src; // return PMA address for continuation
}
static const volatile uint32_t*fromPMA(unsigned btable_addr,byte*dst,unsigned len) {
return fromPMA(bufferAddress(btable_addr),dst,len);
}
// <dst> must be aligned! (divisable by 4)
static volatile uint32_t*toPMA(volatile uint32_t*dst,const byte*src,unsigned len) {
auto s=reinterpret_cast<const uint16_t*>(src);// <src> should be aligned, however, STM32 allows unaligned access
auto l=len>>1;
if (l) do *dst++=*s++; while (--l); // copy 2 bytes
if (len&1) *dst=*reinterpret_cast<const uint8_t*>(s); // copy low byte - continuation impossible
return dst; // return PMA address for continuation
}
static volatile uint32_t*toPMA(unsigned btable_addr,const byte*src,unsigned len) {
return toPMA(bufferAddress(btable_addr),src,len);
}
static USB_SetupPacket setup; // last received setup packet
static byte DevConfig; // current configuration value (0 after USB reset)
// Here, length must not be >64!
// Furthermore, length should not be ==64, as that may require a zero-length-packet after that
static bool send(byte endpoint, const byte*data, unsigned length) {
if (endpoint) {
if (!DevConfig) return false; // don't allow sending over EP1+ when not configured
}else{
if (length>setup.wLength) length=setup.wLength; // limit data length according to setup-requested length
}
toPMA(BTABLE[endpoint].ADDR_TX,data,length);
BTABLE[endpoint].COUNT_TX=length;
SET_TX_STATUS(endpoint,EP_TX_VALID);
return true;
}
static uint32_t flashAddress; // current flash address
static bool onHidOut(uint32_t l) {
if (setup.wLength!=PAGE_SIZE) return false;
const unsigned len=64;
if (l!=len) return false;
auto fa=flashAddress;
if (!(fa&PAGE_SIZE-1)) { // At page start?
LED = 0; // LED on
flash::writeInit(fa);
}
fromPMA(0x18,reinterpret_cast<byte*>(fa),len); // Write page data word-wise directly from USB buffer
fa+=len;
if (!(fa&PAGE_SIZE-1)) { // entire page received?
flash::writeDone();
LED = 1; // LED off;
send(0,0,0); // STATUS stage of Control Out
const byte okay = 1;
send(1,&okay,1); // send OKAY message at end of flash procedure (in theory not needed as there is status stage above)
}
flashAddress=fa;
return true;
}
static bool onHidFeatureOut(uint32_t l) {
if (setup.wLength!=1) return false;
if (l!=1) return false;
fromPMA(0x18,&UploadState,1); // receive 1 byte from feature-out request
if (UploadState==1) { // Reset Page Counter Command
LED = 1; // LED off
flashAddress = USER_PROGRAM;
}
return send(0,0,0); // STATUS stage of Control Out
}
static bool onHidFeatureIn() {
if (setup.wValue!=0x0300) return false; // must be “Feature” and report ID 0
if (setup.wLength!=1) return false; // host must expect 1 byte
return send(0,&UploadState,1);
}
static bool onGetDescriptor() {
const byte*desc;
unsigned length = 0;
switch (setup.wValueH) {
case 1: desc = usbDeviceDesc; break;
case 2: desc = usbConfigDesc; length = sizeof usbConfigDesc; break;
case 0x21: desc = usbConfigDesc+9+9; break;
case 0x22: desc = hidReportDesc; length = sizeof hidReportDesc; break;
case 3: switch (setup.wValueL) {
case 0: desc = usbStringDesc0; break;
case 1: desc = usbStringDesc1; break;
case 2: desc = usbStringDesc2; break;
default: return false;
}break;
default: return false;
}
if (!length) length = desc[0];
return send(0, desc, length);
}
static void initEP1() {
MOD_REG(USB.EPR[1],0x0F0F,1|EP_INTERRUPT|EP_TX_NAK);
}
static void onReset() {
/* Initialize Endpoint 0 */
MOD_REG(USB.EPR[0],0x0F0F,0|EP_CONTROL|EP_RX_VALID);
USB.DADDR = DADDR_EF; // device address = 0 and enable USB function
}
// USB dual-port RAM memory map:
// ARM adr USB adr size (all hex)
// 40006000 0 10 buffer descriptor table (2 entries)
// 40006020 10 8 free for a third BDT entry (e.g. interrupt out)
// 40006030 18 40 EP0RX buffer (SETUP and hid-OUT data)
// 400060B0 58 40 EP0TX buffer (IN data, mostly descriptors)
// 40006130 98 8 EP1TX buffer (interrupt IN)
// 40006140 A0 160 free for more buffers
// 40006400 200 - end of dual-port RAM
// Note that there is no need to copy-out SetupDat out of dual-port RAM;
// members may be accessed directly, but remind the gaps.
// Moreover, there is no need to copy the flash-data twice.
static bool onSetup() {
static const byte DevStatus[2]={0,0};
switch (setup.wRequest) {
//case 0x0081: // Get Status (In, Standard, Iface)
case 0x0080: return send(0,DevStatus,2); // Get Status (In, Standard, Device)
//case 0x0100: // Clear Feature (Out, Standard, Device)
//case 0x0300: // Set Feature (Out, Standard, Device)
case 0x0500: return send(0,0,0); // Set Address (Out, Standard, Device)
case 0x0680: // Get Descriptor (In, Standard, Device)
case 0x0681: return onGetDescriptor(); // Get Descriptor (In, Standard, Iface)
case 0x0880: return send(0,&DevConfig,1); // Get Config (In, Standard, Device)
case 0x0900: DevConfig=setup.wValueL; // Set Config (Out, Standard, Device)
initEP1(); // Reset toggle for EP1
return send(0,0,0);
case 0x0A81: return send(0,DevStatus,1); // Get Iface (In, Standard, Iface)
case 0x0B01: return send(0,0,0); // Set Iface (Out, Standard, Iface)
case 0x01A1: return onHidFeatureIn(); // Get Report (In, Class, Iface)
case 0x0921: return true; // Set Report (Out, Class, Iface): don't stall, data follows
}
return false;
}
static void onEp0() {
uint16_t ep0r = USB.EPR[0];
if (ep0r&EP_CTR_RX) { // OUT and SETUP packets (data reception)
bool ok=false;
if (ep0r&USB_EP0R_SETUP) {
fromPMA(0x18,setup,8); // pull setup data
ok=onSetup();
}else{
uint32_t l = BTABLE[0].COUNT_RX&0x1FF;
if (l) switch (setup.wRequest) { // OUT packet; ignore zero-length status-out packets
case 0x0921: switch (setup.wValue) { // HID::SetReport — setup data is still available
case 0x0200: ok=onHidOut(l); break;
case 0x0300: ok=onHidFeatureOut(l); break;
}break;
}else ok=true;
}
USB.EPR[0]=ok?0x1280:ep0r&0x30^0x1210; // clear EP_CTR_RX bit and change Rx-Nak to Rx-Ready
} // If not ok: Additionally clear EP_CTR_TX bit and set Tx-Stall (Rx never stalls)
// Check for transferred IN packets, i.e. “emptied-out” PMA buffers
// Normally, more data has to been sent here.
// But as all descriptors are less than 64 Bytes in size, no more data has to be processed here.
if (ep0r&EP_CTR_TX) { // Something sent on EP0
if (setup.wRequest==0x0500) USB.DADDR = DADDR_EF|setup.wValueL; // setup data is still available
if (UploadState&2) UploadState|=4; // let terminate main loop after status stage of Hid-Feature-Out
USB.EPR[0]=0x8200; // clear EP_CTR_TX bit but leave Tx-Nak until firmware has something to send
}
}
static void onEp1() {
USB.EPR[1]=1|EP_INTERRUPT; // Endpoint-Interrupt löschen (Hauptschleife weniger belasten)
}
static void onEndpoint(uint16_t status) {
if (status&USB_ISTR_EP_ID) onEp1();
else onEp0();
}
void usb::shutdown() {
USB.CNTR = CNTR_FRES|CNTR_PDWN; // Turn USB Macrocell off
RCC->APB1ENR &=~RCC_APB1ENR_USBEN; // Disable USB Clock on APB1
::revert_sysclock();
}
void usb::init() {
::set_sysclock();
DevConfig = 0;
RCC->APB1ENR |= RCC_APB1ENR_USBEN; /* Enable USB clock */
USB.CNTR = CNTR_FRES; // clear PowerDown bit
/* The following sequence is recommended:
* 1- FRES = 0
* 2- Wait until RESET flag = 1 (polling)
* 3- clear ISTR register */
USB.CNTR = 0;
// setup buffer table addresses (fixed for lifetime)
// As BTABLE[] is defined in 32-bit quantities, this will affect 16-bit words of the PMA correctly.
BTABLE[0].ADDR_TX = 0x58; // ergibt Adresse 0x400060B0
BTABLE[0].ADDR_RX = 0x18; // 0x40006030
BTABLE[1].ADDR_TX = 0x98; // 0x40006200
}
void usb::poll() {
uint16_t istr = USB.ISTR; // save interrupts
USB.ISTR = 0; // clear interrupts
if (istr&ISTR_CTR) onEndpoint(istr); // istr contains endpoint number
if (istr&ISTR_RESET) onReset();
if (istr&ISTR_SUSP) {
// As this device has no USB activity, Windows suspends this after a few seconds.
// Therefore, USB frame counter cannot be used for continuous LED flashing.
// TODO: Reduce and measure power consumption!
USB.CNTR = CNTR_FSUSP|CNTR_LPMODE;
::revert_sysclock();
USB.DADDR = 0; // disable function and reset USB address
}
if (istr&ISTR_WKUP) {
::set_sysclock();
USB.CNTR = 0; // clear FSUSP|LP_MODE
}
}
Detected encoding: UTF-8 | 0
|