summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usb/dwc_otg.h141
-rw-r--r--usb/dwc_otg_def.h87
-rw-r--r--usb/generic.h155
-rw-r--r--usb/usb.h324
4 files changed, 387 insertions, 320 deletions
diff --git a/usb/dwc_otg.h b/usb/dwc_otg.h
new file mode 100644
index 0000000..b68ca0e
--- /dev/null
+++ b/usb/dwc_otg.h
@@ -0,0 +1,141 @@
+#ifndef DWC_OTG_H
+#define DWC_OTG_H
+
+#include "generic.h"
+#include "dwc_otg_def.h"
+
+class USB_otg : public USB_generic {
+ private:
+ DWC_OTG_t& otg;
+
+ uint32_t rxfifo_bytes;
+ uint8_t rxfifo_ep;
+
+ uint32_t setup_buf[16];
+
+ void handle_rxfifo() {
+ uint32_t status = otg.reg.GRXSTSP;
+
+ uint8_t ep = status & 0x4;
+ uint32_t len = (status & 0x7ff0) >> 4;
+ uint32_t type = status & (0xf << 17);
+
+ rxfifo_bytes = len;
+
+ // OUT packet.
+ if(type == (0x2 << 17)) {
+ // TODO: Call endpoint callback.
+ (void)ep;
+ }
+
+ // SETUP packet.
+ if(type == (0x6 << 17)) {
+ for(uint32_t i = 0; i < len; i += 4) {
+ setup_buf[i >> 2] = otg.fifo[0].reg;
+ }
+
+ rxfifo_bytes = 0;
+
+ handle_setup(setup_buf);
+ otg.dev_oep_reg[0].DOEPCTL |= (1 << 26); // CNAK
+ }
+
+ // Discard remaining bytes from FIFO.
+ for(uint32_t i = 0; i < rxfifo_bytes; i += 4) {
+ (void)otg.fifo[0].reg;
+ }
+
+ rxfifo_bytes = 0;
+ }
+
+ protected:
+ virtual void hw_set_address(uint8_t addr) {
+ otg.dev_reg.DCFG |= addr << 4;
+ }
+
+ virtual void hw_conf_ep(uint8_t ep, uint32_t conf) {
+ otg.dev_iep_reg[ep].DIEPCTL = conf;
+ }
+
+ virtual void hw_set_stall(uint8_t ep) {
+ otg.dev_iep_reg[ep].DIEPCTL |= (1 << 21);
+ }
+
+ public:
+ USB_otg(DWC_OTG_t& otg_periph, desc_t dev, desc_t conf) : USB_generic(dev, conf), otg(otg_periph) {}
+
+ void init() {
+ // Set PHYSEL.
+ otg.reg.GUSBCFG |= (1 << 6);
+
+ Time::sleep(10);
+
+ while(!(otg.reg.GRSTCTL & (1 << 31)));
+ otg.reg.GRSTCTL |= 1;
+ while(otg.reg.GRSTCTL & 1);
+
+ otg.reg.GAHBCFG = 0;
+
+ // USB configuration
+ otg.reg.GUSBCFG = (1 << 30) | (0xf << 10) | (0 << 9) | (0 << 8) | (1 << 6);
+ // FDMOD TRDT HNPCAP SRPCAP PHYSEL
+
+ // interrupt mask
+ otg.reg.GINTMSK = (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 3) | (1 << 2) | (1 << 1) | (1 << 4);
+ // ENUMDNEM USBRST USBSUSPM ESUSPM SOFM OTGINT MMISM
+
+ // device configuration
+ otg.dev_reg.DCFG = (1 << 2) | 3;
+ // NZLSOHSK DSPD
+
+ // core configuration
+ otg.reg.GCCFG = (1 << 19) | (1 << 16);
+ // VBUSBSEN PWRDWN
+
+ }
+
+ void process() {
+ // USB reset.
+ if(otg.reg.GINTSTS & (1 << 12)) {
+ otg.dev_oep_reg[0].DOEPCTL = (1 << 27);
+ otg.dev_reg.DAINTMSK = (1 << 16) | 1;
+ otg.dev_reg.DOEPMSK = (1 << 3) | 1;
+ otg.dev_reg.DIEPEMPMSK = (1 << 3) | 1;
+ otg.reg.GRXFSIZ = 256;
+ otg.reg.DIEPTXF0 = (64 << 16) | 256;
+ otg.reg.DIEPTXF1 = (64 << 16) | 320;
+ otg.dev_oep_reg[0].DOEPTSIZ = (3 << 29);
+ }
+
+ // OTG interrupt.
+ if(otg.reg.GINTSTS & (1 << 2)) {
+ otg.reg.GOTGINT = (1 << 2); // SEDET
+ }
+
+ // RxFIFO non-empty.
+ if(otg.reg.GINTSTS & (1 << 4)) {
+ handle_rxfifo();
+ }
+
+ otg.reg.GINTSTS = 0xffffffff;
+ }
+
+ virtual bool ep_ready(uint32_t ep) {
+ return (otg.dev_iep_reg[ep].DIEPCTL & 0x80008000) == 0x8000;
+ }
+
+ virtual void write(uint32_t ep, uint32_t* bufp, uint32_t len) {
+ otg.dev_iep_reg[ep].DIEPTSIZ = (1 << 19) | len;
+ // PKTCNT
+ otg.dev_iep_reg[ep].DIEPCTL |= (1 << 31) | (1 << 26);
+ // EPENA CNAK
+
+ len = (len + 3) >> 2;
+
+ while(len--) {
+ otg.fifo[ep].reg = *bufp++;
+ }
+ }
+};
+
+#endif
diff --git a/usb/dwc_otg_def.h b/usb/dwc_otg_def.h
new file mode 100644
index 0000000..2f19116
--- /dev/null
+++ b/usb/dwc_otg_def.h
@@ -0,0 +1,87 @@
+#ifndef DWC_OTG_DEF_H
+#define DWC_OTG_DEF_H
+
+#include <stdint.h>
+
+class DWC_OTG_t {
+ private:
+ struct DWC_OTG_reg_t {
+ volatile uint32_t GOTGCTL;
+ volatile uint32_t GOTGINT;
+ volatile uint32_t GAHBCFG;
+ volatile uint32_t GUSBCFG;
+ volatile uint32_t GRSTCTL;
+ volatile uint32_t GINTSTS;
+ volatile uint32_t GINTMSK;
+ volatile uint32_t GRXSTSR;
+ volatile uint32_t GRXSTSP;
+ volatile uint32_t GRXFSIZ;
+ volatile uint32_t DIEPTXF0;
+ volatile uint32_t HNPTXSTS;
+ uint32_t _reserved[2];
+ volatile uint32_t GCCFG;
+ volatile uint32_t CID;
+ uint32_t _reserved1[48];
+ volatile uint32_t HPTXFSIZ;
+ volatile uint32_t DIEPTXF1;
+ volatile uint32_t DIEPTXF2;
+ volatile uint32_t DIEPTXF3;
+ };
+
+ struct DWC_OTG_dev_reg_t {
+ volatile uint32_t DCFG;
+ volatile uint32_t DCTL;
+ volatile uint32_t DSTS;
+ uint32_t _reserved;
+ volatile uint32_t DIEPMSK;
+ volatile uint32_t DOEPMSK;
+ volatile uint32_t DAINT;
+ volatile uint32_t DAINTMSK;
+ uint32_t _reserved1[2];
+ volatile uint32_t DVBUSDIS;
+ volatile uint32_t DVBUSPULSE;
+ uint32_t _reserved2;
+ volatile uint32_t DIEPEMPMSK;
+ };
+
+ struct DWC_OTG_dev_iep_reg_t {
+ volatile uint32_t DIEPCTL;
+ uint32_t _reserved;
+ volatile uint32_t DIEPINT;
+ uint32_t _reserved1;
+ volatile uint32_t DIEPTSIZ;
+ uint32_t _reserved2;
+ volatile uint32_t DTXFSTS;
+ uint32_t _reserved3;
+ };
+
+ struct DWC_OTG_dev_oep_reg_t {
+ volatile uint32_t DOEPCTL;
+ uint32_t _reserved;
+ volatile uint32_t DOEPINT;
+ uint32_t _reserved1;
+ volatile uint32_t DOEPTSIZ;
+ uint32_t _reserved2[3];
+ };
+
+ union DWC_OTG_fifo_reg_t {
+ volatile uint32_t reg;
+ volatile uint32_t buf[1024];
+ };
+
+ public:
+ DWC_OTG_reg_t& reg;
+ DWC_OTG_dev_reg_t& dev_reg;
+ DWC_OTG_dev_iep_reg_t* const dev_iep_reg;
+ DWC_OTG_dev_oep_reg_t* const dev_oep_reg;
+ DWC_OTG_fifo_reg_t* const fifo;
+
+ DWC_OTG_t(uint32_t reg_addr) :
+ reg(*(DWC_OTG_reg_t*)reg_addr),
+ dev_reg(*(DWC_OTG_dev_reg_t*)(reg_addr + 0x800)),
+ dev_iep_reg((DWC_OTG_dev_iep_reg_t*)(reg_addr + 0x900)),
+ dev_oep_reg((DWC_OTG_dev_oep_reg_t*)(reg_addr + 0xb00)),
+ fifo((DWC_OTG_fifo_reg_t*)(reg_addr + 0x1000)) {}
+};
+
+#endif
diff --git a/usb/generic.h b/usb/generic.h
new file mode 100644
index 0000000..915b25a
--- /dev/null
+++ b/usb/generic.h
@@ -0,0 +1,155 @@
+#ifndef GENERIC_H
+#define GENERIC_H
+
+#include <stdint.h>
+
+struct desc_t {
+ uint32_t size;
+ void* data;
+};
+
+enum SetupStatus {Unhandled, Ok, Stall};
+
+class USB_class_driver {
+ friend class USB_generic;
+
+ protected:
+ virtual SetupStatus handle_setup(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
+ return SetupStatus::Unhandled;
+ }
+};
+
+class USB_generic {
+ private:
+ desc_t dev_desc;
+ desc_t conf_desc;
+
+ USB_class_driver* control_handlers[4];
+
+ public:
+ USB_generic(desc_t dev, desc_t conf) : dev_desc(dev), conf_desc(conf) {}
+
+ virtual bool ep_ready(uint32_t ep) = 0;
+ virtual void write(uint32_t ep, uint32_t* bufp, uint32_t len) = 0;
+ virtual void hw_set_address(uint8_t addr) = 0;
+ virtual void hw_conf_ep(uint8_t ep, uint32_t conf) = 0;
+ virtual void hw_set_stall(uint8_t ep) = 0;
+
+ bool register_control_handler(USB_class_driver* control_handler) {
+ for(USB_class_driver*& handler : control_handlers) {
+ if(!handler || handler == control_handler) {
+ handler = control_handler;
+ return true;
+ }
+ }
+
+ // Handler table is full.
+ return false;
+ }
+
+ protected:
+ bool get_descriptor(uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
+ desc_t* descp;
+
+ switch(wValue) {
+ case 0x100:
+ descp = &dev_desc;
+ break;
+ case 0x200:
+ descp = &conf_desc;
+ break;
+ default:
+ return false;
+ }
+
+ uint32_t* dp = (uint32_t*)descp->data;
+ uint32_t length = wLength > descp->size ? descp->size : wLength;
+
+ while(length > 64) {
+ write(0, dp, 64);
+ dp += 16;
+ length -= 64;
+
+ while(!ep_ready(0));
+ }
+
+ write(0, dp, length);
+
+ return true;
+ }
+
+ bool set_address(uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
+ hw_set_address(wValue);
+ write(0, 0, 0);
+ return true;
+ }
+
+ bool set_configuration(uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
+ switch(wValue) {
+ case 0:
+ hw_conf_ep(1, 0);
+ break;
+
+ case 1:
+ hw_conf_ep(1, (1 << 22) | (2 << 18) | (1 << 15) | 64);
+ break;
+
+ default:
+ return false;
+ }
+
+ write(0, 0, 0);
+ return true;
+ }
+
+
+ void handle_setup(const uint32_t* bufp) {
+ uint8_t bmRequestType = bufp[0] & 0xff;
+ uint8_t bRequest = (bufp[0] >> 8) & 0xff;
+ uint16_t wValue = (bufp[0] >> 16) & 0xffff;
+ uint16_t wIndex = bufp[1] & 0xffff;
+ uint16_t wLength = (bufp[1] >> 16) & 0xffff;
+
+ // GET_DESCRIPTOR
+ if(bmRequestType == 0x80 && bRequest == 0x06) {
+ if(get_descriptor(wValue, wIndex, wLength)) {
+ return;
+ }
+ }
+
+ // SET_ADDRESS
+ if(bmRequestType == 0x00 && bRequest == 0x05) {
+ if(set_address(wValue, wIndex, wLength)) {
+ return;
+ }
+ }
+
+ // SET_CONFIGURATION
+ if(bmRequestType == 0x00 && bRequest == 0x09) {
+ if(set_configuration(wValue, wIndex, wLength)) {
+ return;
+ }
+ }
+
+ SetupStatus res = SetupStatus::Unhandled;
+
+ for(USB_class_driver*& handler : control_handlers) {
+ if(handler) {
+ res = handler->handle_setup(bmRequestType, bRequest, wValue, wIndex, wLength);
+
+ if(res != SetupStatus::Unhandled) {
+ break;
+ }
+ }
+ }
+
+ if(res == SetupStatus::Ok) {
+ return;
+ }
+
+ hw_set_stall(0);
+ }
+
+};
+
+#endif
diff --git a/usb/usb.h b/usb/usb.h
index 7fb65c0..cefacc4 100644
--- a/usb/usb.h
+++ b/usb/usb.h
@@ -1,327 +1,11 @@
#ifndef USB_H
#define USB_H
-#include <stdint.h>
+#if defined(STM32F4)
+#include "dwc_otg.h"
-struct USB_reg_t {
- volatile uint32_t GOTGCTL;
- volatile uint32_t GOTGINT;
- volatile uint32_t GAHBCFG;
- volatile uint32_t GUSBCFG;
- volatile uint32_t GRSTCTL;
- volatile uint32_t GINTSTS;
- volatile uint32_t GINTMSK;
- volatile uint32_t GRXSTSR;
- volatile uint32_t GRXSTSP;
- volatile uint32_t GRXFSIZ;
- volatile uint32_t DIEPTXF0;
- volatile uint32_t HNPTXSTS;
- uint32_t _reserved[2];
- volatile uint32_t GCCFG;
- volatile uint32_t CID;
- uint32_t _reserved1[48];
- volatile uint32_t HPTXFSIZ;
- volatile uint32_t DIEPTXF1;
- volatile uint32_t DIEPTXF2;
- volatile uint32_t DIEPTXF3;
-};
-
-struct USB_dev_reg_t {
- volatile uint32_t DCFG;
- volatile uint32_t DCTL;
- volatile uint32_t DSTS;
- uint32_t _reserved;
- volatile uint32_t DIEPMSK;
- volatile uint32_t DOEPMSK;
- volatile uint32_t DAINT;
- volatile uint32_t DAINTMSK;
- uint32_t _reserved1[2];
- volatile uint32_t DVBUSDIS;
- volatile uint32_t DVBUSPULSE;
- uint32_t _reserved2;
- volatile uint32_t DIEPEMPMSK;
-};
-
-struct USB_dev_iep_reg_t {
- volatile uint32_t DIEPCTL;
- uint32_t _reserved;
- volatile uint32_t DIEPINT;
- uint32_t _reserved1;
- volatile uint32_t DIEPTSIZ;
- uint32_t _reserved2;
- volatile uint32_t DTXFSTS;
- uint32_t _reserved3;
-};
-
-struct USB_dev_oep_reg_t {
- volatile uint32_t DOEPCTL;
- uint32_t _reserved;
- volatile uint32_t DOEPINT;
- uint32_t _reserved1;
- volatile uint32_t DOEPTSIZ;
- uint32_t _reserved2[3];
-};
-
-union USB_fifo_reg_t {
- volatile uint32_t reg;
- volatile uint32_t buf[1024];
-};
-
-class USB_t {
- public:
- USB_reg_t& reg;
- USB_dev_reg_t& dev_reg;
- USB_dev_iep_reg_t* const dev_iep_reg;
- USB_dev_oep_reg_t* const dev_oep_reg;
- USB_fifo_reg_t* const fifo;
-
- USB_t(uint32_t reg_addr) :
- reg(*(USB_reg_t*)reg_addr),
- dev_reg(*(USB_dev_reg_t*)(reg_addr + 0x800)),
- dev_iep_reg((USB_dev_iep_reg_t*)(reg_addr + 0x900)),
- dev_oep_reg((USB_dev_oep_reg_t*)(reg_addr + 0xb00)),
- fifo((USB_fifo_reg_t*)(reg_addr + 0x1000)) {}
-};
-
-#if defined(STM32F1)
-
-#elif defined(STM32F4)
-static USB_t OTG_FS(0x50000000);
-static USB_t OTG_HS(0x40040000);
+static DWC_OTG_t OTG_FS(0x50000000);
+static DWC_OTG_t OTG_HS(0x40040000);
#endif
-static uint32_t buf[16];
-
-struct desc_t {
- uint32_t size;
- void* data;
-};
-
-class USB_generic {
- private:
- desc_t dev_desc;
- desc_t conf_desc;
-
- public:
- USB_generic(desc_t dev, desc_t conf) : dev_desc(dev), conf_desc(conf) {}
-
- virtual bool ep_ready(uint32_t ep) = 0;
- virtual void write(uint32_t ep, uint32_t* bufp, uint32_t len) = 0;
- virtual void hw_set_address(uint8_t addr) = 0;
- virtual void hw_conf_ep(uint8_t ep, uint32_t conf) = 0;
- virtual void hw_set_stall(uint8_t ep) = 0;
-
- protected:
- bool get_descriptor(uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
- desc_t* descp;
-
- switch(wValue) {
- case 0x100:
- descp = &dev_desc;
- break;
- case 0x200:
- descp = &conf_desc;
- break;
- default:
- return false;
- }
-
- uint32_t* dp = (uint32_t*)descp->data;
- uint32_t length = wLength > descp->size ? descp->size : wLength;
-
- while(length > 64) {
- write(0, dp, 64);
- dp += 16;
- length -= 64;
-
- while(!ep_ready(0));
- }
-
- write(0, dp, length);
-
- return true;
- }
-
- bool set_address(uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
- hw_set_address(wValue);
- write(0, 0, 0);
- return true;
- }
-
- bool set_configuration(uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
- switch(wValue) {
- case 0:
- hw_conf_ep(1, 0);
- break;
-
- case 1:
- hw_conf_ep(1, (1 << 22) | (2 << 18) | (1 << 15) | 64);
- break;
-
- default:
- return false;
- }
-
- write(0, 0, 0);
- return true;
- }
-
-
- void handle_setup(const uint32_t* bufp) {
- uint8_t bmRequestType = bufp[0] & 0xff;
- uint8_t bRequest = (bufp[0] >> 8) & 0xff;
- uint16_t wValue = (bufp[0] >> 16) & 0xffff;
- uint16_t wIndex = bufp[1] & 0xffff;
- uint16_t wLength = (bufp[1] >> 16) & 0xffff;
-
- // GET_DESCRIPTOR
- if(bmRequestType == 0x80 && bRequest == 0x06) {
- if(get_descriptor(wValue, wIndex, wLength)) {
- return;
- }
- }
-
- // SET_ADDRESS
- if(bmRequestType == 0x00 && bRequest == 0x05) {
- if(set_address(wValue, wIndex, wLength)) {
- return;
- }
- }
-
- // SET_CONFIGURATION
- if(bmRequestType == 0x00 && bRequest == 0x09) {
- if(set_configuration(wValue, wIndex, wLength)) {
- return;
- }
- }
-
- // I2C_READ
- //if(bmRequestType == 0xc0 && bRequest == 0xf0) {
- // if(i2c_read(wValue, wIndex, wLength)) {
- // return;
- // }
- //}
-
- // JTAG_SHIFT
- //if(bmRequestType == 0xc0 && bRequest == 0xff) {
- // if(jtag_shift(wValue, wIndex, wLength)) {
- // return;
- // }
- //}
-
- hw_set_stall(0);
- }
-
-};
-
-class USB_otg : public USB_generic {
- private:
- USB_t& otg;
-
- protected:
- virtual void hw_set_address(uint8_t addr) {
- otg.dev_reg.DCFG |= addr << 4;
- }
-
- virtual void hw_conf_ep(uint8_t ep, uint32_t conf) {
- otg.dev_iep_reg[ep].DIEPCTL = conf;
- }
-
- virtual void hw_set_stall(uint8_t ep) {
- otg.dev_iep_reg[ep].DIEPCTL |= (1 << 21);
- }
-
- public:
- USB_otg(USB_t& otg_periph, desc_t dev, desc_t conf) : USB_generic(dev, conf), otg(otg_periph) {}
-
- void init() {
- // Set PHYSEL.
- otg.reg.GUSBCFG |= (1 << 6);
-
- Time::sleep(10);
-
- while(!(otg.reg.GRSTCTL & (1 << 31)));
- otg.reg.GRSTCTL |= 1;
- while(otg.reg.GRSTCTL & 1);
-
- otg.reg.GAHBCFG = 0;
-
- // USB configuration
- otg.reg.GUSBCFG = (1 << 30) | (0xf << 10) | (0 << 9) | (0 << 8) | (1 << 6);
- // FDMOD TRDT HNPCAP SRPCAP PHYSEL
-
- // interrupt mask
- otg.reg.GINTMSK = (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 3) | (1 << 2) | (1 << 1) | (1 << 4);
- // ENUMDNEM USBRST USBSUSPM ESUSPM SOFM OTGINT MMISM
-
- // device configuration
- otg.dev_reg.DCFG = (1 << 2) | 3;
- // NZLSOHSK DSPD
-
- // core configuration
- otg.reg.GCCFG = (1 << 19) | (1 << 16);
- // VBUSBSEN PWRDWN
-
- }
-
- void process() {
- // USB reset.
- if(otg.reg.GINTSTS & (1 << 12)) {
- otg.dev_oep_reg[0].DOEPCTL = (1 << 27);
- otg.dev_reg.DAINTMSK = (1 << 16) | 1;
- otg.dev_reg.DOEPMSK = (1 << 3) | 1;
- otg.dev_reg.DIEPEMPMSK = (1 << 3) | 1;
- otg.reg.GRXFSIZ = 256;
- otg.reg.DIEPTXF0 = (64 << 16) | 256;
- otg.reg.DIEPTXF1 = (64 << 16) | 320;
- otg.dev_oep_reg[0].DOEPTSIZ = (3 << 29);
- }
-
- // OTG interrupt.
- if(otg.reg.GINTSTS & (1 << 2)) {
- otg.reg.GOTGINT = (1 << 2); // SEDET
- }
-
- // RxFIFO non-empty.
- if(otg.reg.GINTSTS & (1 << 4)) {
- handle_rxfifo();
- }
-
- otg.reg.GINTSTS = 0xffffffff;
- }
-
- virtual bool ep_ready(uint32_t ep) {
- return (otg.dev_iep_reg[ep].DIEPCTL & 0x80008000) == 0x8000;
- }
-
- virtual void write(uint32_t ep, uint32_t* bufp, uint32_t len) {
- otg.dev_iep_reg[ep].DIEPTSIZ = (1 << 19) | len;
- // PKTCNT
- otg.dev_iep_reg[ep].DIEPCTL |= (1 << 31) | (1 << 26);
- // EPENA CNAK
-
- len = (len + 3) >> 2;
-
- while(len--) {
- otg.fifo[ep].reg = *bufp++;
- }
- }
-
- void handle_rxfifo() {
- uint32_t status = otg.reg.GRXSTSP;
-
- uint32_t len = (status & 0x7ff0) >> 6;
-
- for(uint32_t i = 0; i < len; i++) {
- buf[i] = otg.fifo[0].reg;
- }
-
- //if(status == 0x000c0080) {
- if((status & (0xf << 17)) == (0x4 << 17)) {
- handle_setup(buf);
- otg.dev_oep_reg[0].DOEPCTL |= (1 << 26);
- }
- }
-};
-
#endif