Source file: /~heha/hsn/bl/bl.zip/usb.cpp

#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-80