#include "usb.h"
/* Realisiert WinUSB/WebUSB, so einfach wie möglich
*/
static byte usbCfg;
enum {
EP0L=6, // Endpoint 0 Size Log2 Control I/O
};
#define W(x) byte(x),byte((x)>>8)
#define D(x) W(x),W((x)>>16)
static const PROGMEM byte DeviceDesc[]={
18, // bLength
1, // bDescType Device
W(0x0200), // USB version supported
0, // bDeviceClass
0, // bDeviceSubclass
0, // bDeviceProtocol
1<<EP0L, // bMaxPacketSize0 64
W(0x16C0), // Voti
W(0x27D8), // libusb = WebUSB
W(0x0200), //
1, // iVendor
2, // iProduct
3, // iSerialNo
1 // bNumConfig
};
static const PROGMEM byte ConfigDesc[9+9]={
9, // bLength
2, // bDescType Config
W(sizeof ConfigDesc), // wTotalLength
1, // bNumInterfaces
1, // bConfigValue
0, // iConfig
0x80, // bmAttributes BusPower
10, // bMaxPower 20 mA
// interface descriptor
9, // bLength
4, // bDescType Iface
0, // bInterfaceNumber
0, // bAlternateSetting (only one)
0, // bNumEndpoints
0xFF, // bIfaceClass vendor-specific
0, // bIfaceSubclass
0, // bIfaceProtocol
0, // iIface
};
// erforderlich für WebUSB für Windows
static const PROGMEM struct{
struct{
word descsize,headertype;
dword windowsversion;
word descsetsize;
}DescSetHeader;
word ConfSubsetHeader[4];
word FuncSubsetHeader[4];
word CompatIdDesc[2];
char compatID[8];
char subcompatID[8];
struct{
word wLength,wDescriptorType,wPropertyDataType,wPropertyNameLength;
char16_t bPropertyName[21];
word wPropertyDataLength;
char16_t bPropertyData[40];
}CUSTOM_PROPERTY;
}MsOs20Desc = {
// Microsoft OS 2.0 descriptor set header (table 10)
{0x000A, // Descriptor size (10 bytes)
0x0000, // MS OS 2.0 descriptor set header
0x06030000, // Windows version (8.1) (0x06030000)
0x00B2}, // Size, MS OS 2.0 descriptor set
// Microsoft OS 2.0 configuration subset header
{0x0008, // Descriptor size (8 bytes)
0x0001, // MS OS 2.0 configuration subset header
0x0000, // bConfigurationValue
0x00A8}, // Size, MS OS 2.0 configuration subset
// Microsoft OS 2.0 function subset header
{0x0008, // Descriptor size (8 bytes)
0x0002, // MS OS 2.0 function subset header
0x0000, // interface number
0x00A0}, // Size, MS OS 2.0 function subset
// Microsoft OS 2.0 compatible ID descriptor (table 13)
{20, // wLength
3}, // MS_OS_20_FEATURE_COMPATIBLE_ID
"WINUSB",
"",
{4+21+1+40<<1,//wLength:
4, // wDescriptorType: MS_OS_20_FEATURE_REG_PROPERTY: 0x04 (Table 9)
7, //wPropertyDataType: REG_MULTI_SZ (Table 15)
21<<1, //wPropertyNameLength:
u"DeviceInterfaceGUIDs", //bPropertyName
40<<1, // wPropertyDataLength
u"{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}" //bPropertyData
}
};
static_assert(sizeof MsOs20Desc==23+66<<1,"Wrong struct!");
static_assert(sizeof MsOs20Desc<256,"Too large!");
// erforderlich für WebUSB:
static const PROGMEM byte BosDesc[5+24+28] = {
5, // Length
0x0F, // Binary Object Store descriptor
W(sizeof BosDesc), // Total length
2, // Number of device capabilities
// WebUSB Platform Capability descriptor (bVendorCode == 0x01).
24, // Length
0x10, // Device Capability descriptor
0x05, // Platform Capability descriptor
0x00, // Reserved
D(0x3408B638),W(0x09A9),W(0x47A0),W(0xFD8B),0xA0,0x76,0x88,0x15,0xB6,0x65, // WebUSB GUID
W(0x0100), // Version 1.0
0x01, // Vendor request code (-> bRequest)
0x01, // Landing page (-> wValueL)
// Microsoft OS 2.0 Platform Capability Descriptor (MS_VendorCode == 0x02)
28, // Length
0x10, // Device Capability descriptor
0x05, // Platform Capability descriptor
0x00, // Reserved
D(0xD8DD60DF),W(0x4589),W(0x4CC7),W(0xD29C),0x65,0x9D,0x9E,0x64,0x8A,0x9F, // MS OS 2.0 GUID
D(0x06030000), // Windows version
W(sizeof MsOs20Desc), // Descriptor set length
0x02, // Vendor request code (-> bRequest)
0x00 // Alternate enumeration code
};
static_assert(sizeof BosDesc==57,"Wrong struct!");
#undef D
#undef W
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++*
* Automatisch abgezählter URL- und String-Desktiptor *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
template<typename T>constexpr size_t length(const T*s) {
return *s ? 1+length(s+1) : 0; // zählt nullterminierte Strings
}
template<typename T,size_t N>struct array{ // ähnlich std::array
T a[N];
constexpr operator const T*() const {return a;}
constexpr operator T*() {return a;}
template<size_t M> constexpr auto operator+(const array<T,M>&&b) const {
array<T,N+M> K={};
size_t k=0;
for(size_t i=0; i<N; i++,k++) K[k]=a[i];
for(size_t i=0; i<M; i++,k++) K[k]=b[i];
return K;
}
};
template<size_t N>constexpr auto urldesc(byte proto, const char*s) {
constexpr auto L=3+N;
static_assert(L<256,"String zu lang!");
array<byte,L> A={L,3,proto};
for (size_t i=0; i<N; i++) A[3+i] = s[i];
return A;
}
template<size_t N>constexpr auto strdesc(const char16_t*s) {
constexpr auto L=2+N+N;
static_assert(L<256,"String zu lang!");
array<byte,L> A={L,3};
for (size_t i=0; i<N; i++) {A[2+i+i] = s[i]&0xFF; A[3+i+i] = s[i]>>8;}
return A;
}
#define URLDESC(p,s) (urldesc<length(u8##s)>(p,s))
#define STRDESC(s) (strdesc<length(u##s)>(u##s))
/*++++++++++++++++++++++++++++++*
* Ende Template-Hokuspokus *
*++++++++++++++++++++++++++++++*/
// erforderlich für WebUSB
static const PROGMEM auto UrlDesc =
URLDESC(1,"www.tu-chemnitz.de/~heha/mb-iwp/Bergwerk/app.htm");
static const PROGMEM auto StrDesc =
STRDESC("\x0407")+ // (0) deutsch
STRDESC("TU Chemnitz, IWP")+ // (1)
STRDESC("USB-I²C-Adapter")+ // (2)
STRDESC("heha@hrz.tu-chemnitz.de"); // (3)
#undef STRDESC
#undef URLDESC
// Nur von Assembler aufzurufen, macht *Z++ aus Daten- oder Flashspeicher
extern "C" void getbyte() { asm(
" sbrs ZH,7 \n"
" ld r0,Z+ \n"
" sbrc ZH,7 \n"
" lpm r0,Z+ \n");}
void usb::Init() {
UHWCON=1;
USBCON=0xB0; // Ubus aktivieren, aber ohne Takt
USBINT=1; // beim nächsten usbPoll USB aktivieren oder deaktivieren lassen
} // Dokumentationsfehler: USBCON.7 muss 1 sein damit UDCON.0 0 bleibt!
namespace usb{
static bool Ep0Send(const void*,byte);
}
static bool usb::Ep0Send(const void*addr, byte len) {
bool zlp=len<setup.wLength; // Null-Byte-Paket als Abschluss bei glatter Transferlänge?
if (len>setup.wLength) len=setup.wLength;
UENUM=0;
byte n;
do{
while (!((n=UEINTX)&0x0D)); // warten bis Interrupt — oder USB abgesteckt
if (n&0x0C) return false; // Ende bei (unerwartetem) OUT- oder SETUP-Paket (15h kommt vorbei!)
n=1<<EP0L; if (n>len) n=len;
if (n) asm volatile(
" push %2 \n"
"1: rcall getbyte \n"
" sts %1,r0 \n"
" dec %2 \n"
" brne 1b \n"
" pop %2 \n"
:"+z"(addr):"m"(UEDATX),"r"(n));
// do UEDATX=pgm_read_byte(addr++); while (--n);
UEINTX=0b1111'1110; // Gefüllte FIFO senden
}while (len-=n || zlp && n==1<<EP0L);
return true;
}
usb::SetupDesc usb::setup NOINIT; // aktueller Setup-Transfer-Header
static void usbPollEp0() {
UENUM=0; // Endpoint 0 betreffend
if (UEINTX&4) { // OUT angekommen?
UEINTX=~4; // Daten ignorieren
}
if (UEINTX&8) { // SETUP angekommen?
byte*ptr=usb::setup;
// Das SETUP-Paket in einzelne Bytes zu stecken ermöglicht Optimierung,
// sofern nicht Unterprogramme (damit) aufgerufen werden müssen.
byte bmRequestType=*ptr++=UEDATX;
byte bRequest=*ptr++=UEDATX;
byte wValueL=*ptr++=UEDATX;
byte wValueH=*ptr++=UEDATX;
*ptr++=UEDATX;
*ptr++=UEDATX;
*ptr++=UEDATX;
*ptr++=UEDATX;
UEINTX=~8; // Jetzt erst(!) alles löschen außer TXINI
byte len;
const byte*addr; // kann in RAM oder Flash (≥ 0x8000) zeigen
#define RETURN_OKAY {UEINTX=0; return;}
#define RETURN_STALL {UEINTX=0; UECONX=0x21; return;}
switch (bmRequestType) {
// Standard-Requests
case 0x00: switch (bRequest) { // OUT, DEVICE
case 1: // Clear Feature
case 3: RETURN_OKAY; // Set Feature
case 5: { // Set Address (wValueL)
UEINTX=0; // mit 0-Byte-IN-Paket beantworten
while (!(UEINTX&1)); // warten bis abgeschickt (TODO: Deadlock bei USB-Reset!)
UDADDR=wValueL|0x80; // jetzt erst Adresse setzen
}return;
case 9: {
usbCfg=wValueL;
RETURN_OKAY;
}break;
}break;
case 0x01: switch (bRequest) { // OUT, STD, IFACE[wIndex]
case 11: RETURN_OKAY; // Set Interface (ignorieren)
}break;
case 0x40: switch (bRequest) { // OUT, VENDOR, DEVICE
case 0xA0: if (i2c::send()) RETURN_OKAY; break;
}break;
case 0x80: switch (bRequest) { // IN, STD, DEVICE
case 0: { // Get Status
UEDATX=0; // Bus Powered
UEDATX=0;
}RETURN_OKAY;
case 6: switch (wValueH) { // Get Descriptor
case 1: addr=FP(DeviceDesc); goto gd1;
case 2: addr=FP(ConfigDesc); len=sizeof ConfigDesc; goto gd2;
case 3: {
if (wValueL>3) RETURN_STALL;
for (addr=FP(StrDesc);wValueL;wValueL--) addr+=pgm_read_byte(addr);
}goto gd1;
case 15: addr=FP(BosDesc); len=sizeof BosDesc; goto gd2;
}RETURN_STALL;
case 8: { // Get Configuration
UEDATX=usbCfg;
}RETURN_OKAY;
}break;
case 0x81: switch (bRequest) { // IN, STD, IFACE[wIndex]
case 0: { // Get Status
UEDATX=0;
UEDATX=0;
}RETURN_OKAY;
case 10: { // Get Interface (= Alternate Setting)
UEDATX=0;
}RETURN_OKAY;
}break;
case 0xC0: switch (bRequest) { // IN, VENDOR, DEVICE
case 2: addr=FP(&MsOs20Desc); len=sizeof MsOs20Desc; goto gd2; // wIndex=7
case 1: addr=FP(UrlDesc); // wIndex=2, wValueL=1
gd1: len=pgm_read_byte(addr);
gd2: usb::Ep0Send(addr,len);
return;
case 0xA0: if (i2c::recv()) return; break;
case 0xA1: UEDATX = i2c::lastError; RETURN_OKAY;
}break;
}
RETURN_STALL; // Alles andere: STALL aktivieren
}
#undef RETURN_OKAY
#undef RETURN_STALL
}
void usb::Poll() {
if (USBINT&1) { // Ab- oder Anstecken des Hosts?
USBINT=0;
if (USBSTA&1) { // Angesteckt
UDCON=0; // Pullup aktivieren
}else{ // Abgesteckt
UDCON=1; // Pullup deaktivieren
USBCON=0xB0; // Takt deaktivieren
usbCfg=0;
}
}
byte udint=UDINT;
UDINT=0;
if (udint&0x10 && USBCON&0x20) { // Aktivität && Takt eingefroren?
PLLCSR=0x12; // PLL aktivieren (für 16-MHz-Quarz)
while(!(PLLCSR&1)); // warten bis eingerastet
USBCON=0x90; // Takt aktivieren
}
if (udint&1) { // USB-Suspend
USBCON=0xB0; // Takt deaktivieren
PLLCSR=0; // PLL deaktivieren
}
if (udint&8) { // USB-Reset-Ende?
usbCfg=0; // Nicht konfiguriert (Adresse wird von Hardware zurückgesetzt)
UENUM=0;
UECONX=1; // Endpoint 0 aktivieren
UECFG1X=2|EP0L-3<<4; // Bytes für Endpoint 0 im DPRAM reservieren
}
usbPollEp0();
}
Detected encoding: UTF-8 | 0
|