STM32 F1 USB: Fix OUT SETUP command bug. From David Sidrane
This commit is contained in:
parent
55b592faf8
commit
d8d469fa58
@ -5992,3 +5992,6 @@
|
||||
the STM32F429. From Ken Pettit (2013-11-7).
|
||||
* configs/stm32f429i-disco: Support for the STM32F429I-Discovery
|
||||
board from Ken Pettit (2013-11-7).
|
||||
* arch/arm/src/stm32/stm32_usbdev.c: The long outstanding bug
|
||||
involviing the handling of OUT SETUP commands has been fixed in
|
||||
the STM32 F1 USB device driver by David Sidrane (2013-11-7).
|
||||
|
4
TODO
4
TODO
@ -961,6 +961,10 @@ o USB (drivers/usbdev, drivers/usbhost)
|
||||
correctly supports OUT SETUP data following the same design as
|
||||
per above.
|
||||
|
||||
Update 2013-11-7: David Sidrane has fixed with issue with the
|
||||
STM32 F1 USB device driver. Still a few more to go before this
|
||||
can be closed out.
|
||||
|
||||
Status: Open
|
||||
Priority: High for class drivers that need EP0 data. For example, the
|
||||
CDC/ACM serial driver might need the line coding data (that
|
||||
|
@ -77,6 +77,10 @@
|
||||
# define CONFIG_USBDEV_EP0_MAXSIZE 64
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_USBDEV_SETUP_MAXDATASIZE
|
||||
# define CONFIG_USBDEV_SETUP_MAXDATASIZE CONFIG_USBDEV_EP0_MAXSIZE
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_USB_PRI
|
||||
# define CONFIG_USB_PRI NVIC_SYSH_PRIORITY_DEFAULT
|
||||
#endif
|
||||
@ -134,7 +138,7 @@
|
||||
#define STM32_MAXPACKET_SIZE (1 << (STM32_MAXPACKET_SHIFT))
|
||||
#define STM32_MAXPACKET_MASK (STM32_MAXPACKET_SIZE-1)
|
||||
|
||||
#define STM32_EP0MAXPACKET STM32_MAXPACKET_SIZE
|
||||
#define STM32_EP0MAXPACKET STM32_MAXPACKET_SIZE
|
||||
|
||||
/* Buffer descriptor table. We assume that USB has exclusive use of CAN/USB
|
||||
* memory. The buffer table is positioned at the beginning of the 512-byte
|
||||
@ -240,6 +244,8 @@
|
||||
#define STM32_TRACEINTID_SUSP 0x001c
|
||||
#define STM32_TRACEINTID_SYNCHFRAME 0x001d
|
||||
#define STM32_TRACEINTID_WKUP 0x001e
|
||||
#define STM32_TRACEINTID_EP0SETUPOUT 0x001f
|
||||
#define STM32_TRACEINTID_EP0SETUPOUTDATA 0x0020
|
||||
|
||||
/* Ever-present MIN and MAX macros */
|
||||
|
||||
@ -267,17 +273,20 @@
|
||||
|
||||
/* The various states of a control pipe */
|
||||
|
||||
enum stm32_ep0state_e
|
||||
enum stm32_ep0state_e
|
||||
{
|
||||
EP0STATE_IDLE = 0, /* No request in progress */
|
||||
EP0STATE_RDREQUEST, /* Read request in progress */
|
||||
EP0STATE_SETUP_OUT, /* Set up recived with data for device OUT in progress */
|
||||
EP0STATE_SETUP_READY, /* Set up was recived prior and is in ctrl,
|
||||
* now the data has arrived */
|
||||
EP0STATE_WRREQUEST, /* Write request in progress */
|
||||
EP0STATE_RDREQUEST, /* Read request in progress */
|
||||
EP0STATE_STALLED /* We are stalled */
|
||||
};
|
||||
|
||||
/* Resume states */
|
||||
|
||||
enum stm32_rsmstate_e
|
||||
enum stm32_rsmstate_e
|
||||
{
|
||||
RSMSTATE_IDLE = 0, /* Device is either fully suspended or running */
|
||||
RSMSTATE_STARTED, /* Resume sequence has been started */
|
||||
@ -294,8 +303,8 @@ union wb_u
|
||||
|
||||
struct stm32_req_s
|
||||
{
|
||||
struct usbdev_req_s req; /* Standard USB request */
|
||||
struct stm32_req_s *flink; /* Supports a singly linked list */
|
||||
struct usbdev_req_s req; /* Standard USB request */
|
||||
struct stm32_req_s *flink; /* Supports a singly linked list */
|
||||
};
|
||||
|
||||
/* This is the internal representation of an endpoint */
|
||||
@ -307,7 +316,7 @@ struct stm32_ep_s
|
||||
* to struct stm32_ep_s.
|
||||
*/
|
||||
|
||||
struct usbdev_ep_s ep; /* Standard endpoint structure */
|
||||
struct usbdev_ep_s ep; /* Standard endpoint structure */
|
||||
|
||||
/* STR71X-specific fields */
|
||||
|
||||
@ -336,21 +345,41 @@ struct stm32_usbdev_s
|
||||
|
||||
/* STM32-specific fields */
|
||||
|
||||
struct usb_ctrlreq_s ctrl; /* Last EP0 request */
|
||||
uint8_t ep0state; /* State of EP0 (see enum stm32_ep0state_e) */
|
||||
uint8_t rsmstate; /* Resume state (see enum stm32_rsmstate_e) */
|
||||
uint8_t nesofs; /* ESOF counter (for resume support) */
|
||||
uint8_t rxpending:1; /* 1: OUT data in PMA, but no read requests */
|
||||
uint8_t selfpowered:1; /* 1: Device is self powered */
|
||||
uint8_t epavail; /* Bitset of available endpoints */
|
||||
uint8_t bufavail; /* Bitset of available buffers */
|
||||
uint16_t rxstatus; /* Saved during interrupt processing */
|
||||
uint16_t txstatus; /* " " " " " " " " */
|
||||
uint16_t imask; /* Current interrupt mask */
|
||||
uint8_t ep0state; /* State of EP0 (see enum stm32_ep0state_e) */
|
||||
uint8_t rsmstate; /* Resume state (see enum stm32_rsmstate_e) */
|
||||
uint8_t nesofs; /* ESOF counter (for resume support) */
|
||||
uint8_t rxpending:1; /* 1: OUT data in PMA, but no read requests */
|
||||
uint8_t selfpowered:1; /* 1: Device is self powered */
|
||||
uint8_t epavail; /* Bitset of available endpoints */
|
||||
uint8_t bufavail; /* Bitset of available buffers */
|
||||
uint16_t rxstatus; /* Saved during interrupt processing */
|
||||
uint16_t txstatus; /* " " " " " " " " */
|
||||
uint16_t imask; /* Current interrupt mask */
|
||||
|
||||
/* E0 SETUP data buffering.
|
||||
*
|
||||
* ctrl
|
||||
* The 8-byte SETUP request is received on the EP0 OUT endpoint and is
|
||||
* saved.
|
||||
*
|
||||
* ep0data
|
||||
* For OUT SETUP requests, the SETUP data phase must also complete before
|
||||
* the SETUP command can be processed. The ep0 packet receipt logic
|
||||
* stm32_ep0_rdrequest will save the accompanying EP0 OUT data in
|
||||
* ep0data[] before the SETUP command is re-processed.
|
||||
*
|
||||
* ep0datlen
|
||||
* Lenght of OUT DATA received in ep0data[]
|
||||
*/
|
||||
|
||||
struct usb_ctrlreq_s ctrl; /* Last EP0 request */
|
||||
|
||||
uint8_t ep0data[CONFIG_USBDEV_SETUP_MAXDATASIZE];
|
||||
uint16_t ep0datlen;
|
||||
|
||||
/* The endpoint list */
|
||||
|
||||
struct stm32_ep_s eplist[STM32_NENDPOINTS];
|
||||
struct stm32_ep_s eplist[STM32_NENDPOINTS];
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
@ -438,6 +467,8 @@ static int stm32_wrrequest(struct stm32_usbdev_s *priv,
|
||||
inline static int
|
||||
stm32_wrrequest_ep0(struct stm32_usbdev_s *priv,
|
||||
struct stm32_ep_s *privep);
|
||||
static inline int
|
||||
stm32_ep0_rdrequest(struct stm32_usbdev_s *priv);
|
||||
static int stm32_rdrequest(struct stm32_usbdev_s *priv,
|
||||
struct stm32_ep_s *privep);
|
||||
static void stm32_cancelrequests(struct stm32_ep_s *privep);
|
||||
@ -570,6 +601,8 @@ const struct trace_msg_t g_usb_trace_strings_intdecode[] =
|
||||
TRACE_STR(STM32_TRACEINTID_SUSP ),
|
||||
TRACE_STR(STM32_TRACEINTID_SYNCHFRAME ),
|
||||
TRACE_STR(STM32_TRACEINTID_WKUP ),
|
||||
TRACE_STR(STM32_TRACEINTID_EP0SETUPOUT ),
|
||||
TRACE_STR(STM32_TRACEINTID_EP0SETUPOUTDATA ),
|
||||
TRACE_STR_END
|
||||
};
|
||||
#endif
|
||||
@ -577,33 +610,33 @@ const struct trace_msg_t g_usb_trace_strings_intdecode[] =
|
||||
#ifdef CONFIG_USBDEV_TRACE_STRINGS
|
||||
const struct trace_msg_t g_usb_trace_strings_deverror[] =
|
||||
{
|
||||
TRACE_STR(STM32_TRACEERR_ALLOCFAIL ),
|
||||
TRACE_STR(STM32_TRACEERR_BADCLEARFEATURE ),
|
||||
TRACE_STR(STM32_TRACEERR_BADDEVGETSTATUS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADEPGETSTATUS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADEPNO ),
|
||||
TRACE_STR(STM32_TRACEERR_BADEPTYPE ),
|
||||
TRACE_STR(STM32_TRACEERR_BADGETCONFIG ),
|
||||
TRACE_STR(STM32_TRACEERR_BADGETSETDESC ),
|
||||
TRACE_STR(STM32_TRACEERR_BADGETSTATUS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADSETADDRESS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADSETCONFIG ),
|
||||
TRACE_STR(STM32_TRACEERR_BADSETFEATURE ),
|
||||
TRACE_STR(STM32_TRACEERR_BINDFAILED ),
|
||||
TRACE_STR(STM32_TRACEERR_DISPATCHSTALL ),
|
||||
TRACE_STR(STM32_TRACEERR_DRIVER ),
|
||||
TRACE_STR(STM32_TRACEERR_DRIVERREGISTERED),
|
||||
TRACE_STR(STM32_TRACEERR_EP0BADCTR ),
|
||||
TRACE_STR(STM32_TRACEERR_EP0SETUPSTALLED ),
|
||||
TRACE_STR(STM32_TRACEERR_EPBUFFER ),
|
||||
TRACE_STR(STM32_TRACEERR_EPDISABLED ),
|
||||
TRACE_STR(STM32_TRACEERR_EPOUTNULLPACKET ),
|
||||
TRACE_STR(STM32_TRACEERR_EPRESERVE ),
|
||||
TRACE_STR(STM32_TRACEERR_INVALIDCTRLREQ ),
|
||||
TRACE_STR(STM32_TRACEERR_INVALIDPARMS ),
|
||||
TRACE_STR(STM32_TRACEERR_IRQREGISTRATION ),
|
||||
TRACE_STR(STM32_TRACEERR_NOTCONFIGURED ),
|
||||
TRACE_STR(STM32_TRACEERR_REQABORTED ),
|
||||
TRACE_STR(STM32_TRACEERR_ALLOCFAIL ),
|
||||
TRACE_STR(STM32_TRACEERR_BADCLEARFEATURE ),
|
||||
TRACE_STR(STM32_TRACEERR_BADDEVGETSTATUS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADEPGETSTATUS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADEPNO ),
|
||||
TRACE_STR(STM32_TRACEERR_BADEPTYPE ),
|
||||
TRACE_STR(STM32_TRACEERR_BADGETCONFIG ),
|
||||
TRACE_STR(STM32_TRACEERR_BADGETSETDESC ),
|
||||
TRACE_STR(STM32_TRACEERR_BADGETSTATUS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADSETADDRESS ),
|
||||
TRACE_STR(STM32_TRACEERR_BADSETCONFIG ),
|
||||
TRACE_STR(STM32_TRACEERR_BADSETFEATURE ),
|
||||
TRACE_STR(STM32_TRACEERR_BINDFAILED ),
|
||||
TRACE_STR(STM32_TRACEERR_DISPATCHSTALL ),
|
||||
TRACE_STR(STM32_TRACEERR_DRIVER ),
|
||||
TRACE_STR(STM32_TRACEERR_DRIVERREGISTERED ),
|
||||
TRACE_STR(STM32_TRACEERR_EP0BADCTR ),
|
||||
TRACE_STR(STM32_TRACEERR_EP0SETUPSTALLED ),
|
||||
TRACE_STR(STM32_TRACEERR_EPBUFFER ),
|
||||
TRACE_STR(STM32_TRACEERR_EPDISABLED ),
|
||||
TRACE_STR(STM32_TRACEERR_EPOUTNULLPACKET ),
|
||||
TRACE_STR(STM32_TRACEERR_EPRESERVE ),
|
||||
TRACE_STR(STM32_TRACEERR_INVALIDCTRLREQ ),
|
||||
TRACE_STR(STM32_TRACEERR_INVALIDPARMS ),
|
||||
TRACE_STR(STM32_TRACEERR_IRQREGISTRATION ),
|
||||
TRACE_STR(STM32_TRACEERR_NOTCONFIGURED ),
|
||||
TRACE_STR(STM32_TRACEERR_REQABORTED ),
|
||||
TRACE_STR_END
|
||||
};
|
||||
#endif
|
||||
@ -611,7 +644,7 @@ const struct trace_msg_t g_usb_trace_strings_deverror[] =
|
||||
/****************************************************************************
|
||||
* Private Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* Register Operations
|
||||
****************************************************************************/
|
||||
@ -761,11 +794,11 @@ static void stm32_checksetup(void)
|
||||
* Name: stm32_seteptxcount
|
||||
****************************************************************************/
|
||||
|
||||
static inline void stm32_seteptxcount(uint8_t epno, uint16_t count)
|
||||
static inline void stm32_seteptxcount(uint8_t epno, uint16_t count)
|
||||
{
|
||||
volatile uint32_t *epaddr = (uint32_t*)STM32_USB_COUNT_TX(epno);
|
||||
*epaddr = count;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_seteptxaddr
|
||||
@ -791,7 +824,7 @@ static inline uint16_t stm32_geteptxaddr(uint8_t epno)
|
||||
* Name: stm32_seteprxcount
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_seteprxcount(uint8_t epno, uint16_t count)
|
||||
static void stm32_seteprxcount(uint8_t epno, uint16_t count)
|
||||
{
|
||||
volatile uint32_t *epaddr = (uint32_t*)STM32_USB_COUNT_RX(epno);
|
||||
uint32_t rxcount = 0;
|
||||
@ -832,7 +865,7 @@ static void stm32_seteprxcount(uint8_t epno, uint16_t count)
|
||||
rxcount = (uint32_t)(nblocks << USB_COUNT_RX_NUM_BLOCK_SHIFT);
|
||||
}
|
||||
*epaddr = rxcount;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_geteprxcount
|
||||
@ -868,7 +901,7 @@ static inline uint16_t stm32_geteprxaddr(uint8_t epno)
|
||||
* Name: stm32_setepaddress
|
||||
****************************************************************************/
|
||||
|
||||
static inline void stm32_setepaddress(uint8_t epno, uint16_t addr)
|
||||
static inline void stm32_setepaddress(uint8_t epno, uint16_t addr)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -878,7 +911,7 @@ static inline void stm32_setepaddress(uint8_t epno, uint16_t addr)
|
||||
regval &= ~USB_EPR_EA_MASK;
|
||||
regval |= (addr << USB_EPR_EA_SHIFT);
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_seteptype
|
||||
@ -940,7 +973,7 @@ static inline void stm32_clrstatusout(uint8_t epno)
|
||||
* Name: stm32_clrrxdtog
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_clrrxdtog(uint8_t epno)
|
||||
static void stm32_clrrxdtog(uint8_t epno)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -951,14 +984,14 @@ static void stm32_clrrxdtog(uint8_t epno)
|
||||
regval &= EPR_NOTOG_MASK;
|
||||
regval |= USB_EPR_DTOG_RX;
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_clrtxdtog
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_clrtxdtog(uint8_t epno)
|
||||
static void stm32_clrtxdtog(uint8_t epno)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -970,13 +1003,13 @@ static void stm32_clrtxdtog(uint8_t epno)
|
||||
regval |= USB_EPR_DTOG_TX;
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_clrepctrrx
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_clrepctrrx(uint8_t epno)
|
||||
static void stm32_clrepctrrx(uint8_t epno)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -985,13 +1018,13 @@ static void stm32_clrepctrrx(uint8_t epno)
|
||||
regval &= EPR_NOTOG_MASK;
|
||||
regval &= ~USB_EPR_CTR_RX;
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_clrepctrtx
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_clrepctrtx(uint8_t epno)
|
||||
static void stm32_clrepctrtx(uint8_t epno)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -1000,13 +1033,13 @@ static void stm32_clrepctrtx(uint8_t epno)
|
||||
regval &= EPR_NOTOG_MASK;
|
||||
regval &= ~USB_EPR_CTR_TX;
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_geteptxstatus
|
||||
****************************************************************************/
|
||||
|
||||
static inline uint16_t stm32_geteptxstatus(uint8_t epno)
|
||||
static inline uint16_t stm32_geteptxstatus(uint8_t epno)
|
||||
{
|
||||
return (uint16_t)(stm32_getreg(STM32_USB_EPR(epno)) & USB_EPR_STATTX_MASK);
|
||||
}
|
||||
@ -1015,7 +1048,7 @@ static inline uint16_t stm32_geteptxstatus(uint8_t epno)
|
||||
* Name: stm32_geteprxstatus
|
||||
****************************************************************************/
|
||||
|
||||
static inline uint16_t stm32_geteprxstatus(uint8_t epno)
|
||||
static inline uint16_t stm32_geteprxstatus(uint8_t epno)
|
||||
{
|
||||
return (stm32_getreg(STM32_USB_EPR(epno)) & USB_EPR_STATRX_MASK);
|
||||
}
|
||||
@ -1024,7 +1057,7 @@ static inline uint16_t stm32_geteprxstatus(uint8_t epno)
|
||||
* Name: stm32_seteptxstatus
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_seteptxstatus(uint8_t epno, uint16_t state)
|
||||
static void stm32_seteptxstatus(uint8_t epno, uint16_t state)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -1043,13 +1076,13 @@ static void stm32_seteptxstatus(uint8_t epno, uint16_t state)
|
||||
regval ^= state;
|
||||
regval &= EPR_TXDTOG_MASK;
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_seteprxstatus
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_seteprxstatus(uint8_t epno, uint16_t state)
|
||||
static void stm32_seteprxstatus(uint8_t epno, uint16_t state)
|
||||
{
|
||||
uint32_t epaddr = STM32_USB_EPR(epno);
|
||||
uint16_t regval;
|
||||
@ -1068,13 +1101,13 @@ static void stm32_seteprxstatus(uint8_t epno, uint16_t state)
|
||||
regval ^= state;
|
||||
regval &= EPR_RXDTOG_MASK;
|
||||
stm32_putreg(regval, epaddr);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_eptxstalled
|
||||
****************************************************************************/
|
||||
|
||||
static inline bool stm32_eptxstalled(uint8_t epno)
|
||||
static inline bool stm32_eptxstalled(uint8_t epno)
|
||||
{
|
||||
return (stm32_geteptxstatus(epno) == USB_EPR_STATTX_STALL);
|
||||
}
|
||||
@ -1083,7 +1116,7 @@ static inline bool stm32_eptxstalled(uint8_t epno)
|
||||
* Name: stm32_eprxstalled
|
||||
****************************************************************************/
|
||||
|
||||
static inline bool stm32_eprxstalled(uint8_t epno)
|
||||
static inline bool stm32_eprxstalled(uint8_t epno)
|
||||
{
|
||||
return (stm32_geteprxstatus(epno) == USB_EPR_STATRX_STALL);
|
||||
}
|
||||
@ -1095,7 +1128,7 @@ static inline bool stm32_eprxstalled(uint8_t epno)
|
||||
* Name: stm32_copytopma
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_copytopma(const uint8_t *buffer, uint16_t pma, uint16_t nbytes)
|
||||
static void stm32_copytopma(const uint8_t *buffer, uint16_t pma, uint16_t nbytes)
|
||||
{
|
||||
uint16_t *dest;
|
||||
uint16_t ms;
|
||||
@ -1127,7 +1160,7 @@ static void stm32_copytopma(const uint8_t *buffer, uint16_t pma, uint16_t nbytes
|
||||
****************************************************************************/
|
||||
|
||||
static inline void
|
||||
stm32_copyfrompma(uint8_t *buffer, uint16_t pma, uint16_t nbytes)
|
||||
stm32_copyfrompma(uint8_t *buffer, uint16_t pma, uint16_t nbytes)
|
||||
{
|
||||
uint32_t *src;
|
||||
int nwords = (nbytes + 1) >> 1;
|
||||
@ -1317,7 +1350,7 @@ static int stm32_wrrequest(struct stm32_usbdev_s *priv, struct stm32_ep_s *prive
|
||||
/* We get here when an IN endpoint interrupt occurs. So now we know that
|
||||
* there is no TX transfer in progress.
|
||||
*/
|
||||
|
||||
|
||||
privep->txbusy = false;
|
||||
|
||||
/* Check the request from the head of the endpoint request queue */
|
||||
@ -1404,6 +1437,50 @@ static int stm32_wrrequest(struct stm32_usbdev_s *priv, struct stm32_ep_s *prive
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Name: stm32_ep0_rdrequest
|
||||
*
|
||||
* Description:
|
||||
* This function is called from the stm32_ep0out handler when the ep0state
|
||||
* is EP0STATE_SETUP_OUT and uppon new incoming data is available in the endpoint
|
||||
* 0's buffer. This function will simply copy the OUT data into ep0data.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
static inline int stm32_ep0_rdrequest(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
uint32_t src;
|
||||
int pmalen;
|
||||
int readlen;
|
||||
|
||||
/* Get the number of bytes to read from packet memory */
|
||||
|
||||
pmalen = stm32_geteprxcount(EP0);
|
||||
|
||||
ullvdbg("EP0: pmalen=%d\n", pmalen);
|
||||
usbtrace(TRACE_READ(EP0), pmalen);
|
||||
|
||||
/* Read the data into our special buffer for SETUP data */
|
||||
|
||||
readlen = MIN(CONFIG_USBDEV_SETUP_MAXDATASIZE, pmalen);
|
||||
src = stm32_geteprxaddr(EP0);
|
||||
|
||||
/* Receive the next packet */
|
||||
|
||||
stm32_copyfrompma(&priv->ep0data[0], src, readlen);
|
||||
|
||||
/* Now we can process the setup command */
|
||||
|
||||
priv->ep0state = EP0STATE_SETUP_READY;
|
||||
priv->ep0datlen = readlen;
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EP0SETUPOUTDATA), readlen);
|
||||
|
||||
stm32_ep0setup(priv);
|
||||
priv->ep0datlen = 0; /* mark the date consumed */
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_rdrequest
|
||||
****************************************************************************/
|
||||
@ -1505,7 +1582,8 @@ static void stm32_dispatchrequest(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
/* Forward to the control request to the class driver implementation */
|
||||
|
||||
ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl, NULL, 0);
|
||||
ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl,
|
||||
priv->ep0data, priv->ep0datlen);
|
||||
if (ret < 0)
|
||||
{
|
||||
/* Stall on failure */
|
||||
@ -1525,7 +1603,7 @@ static void stm32_epdone(struct stm32_usbdev_s *priv, uint8_t epno)
|
||||
struct stm32_ep_s *privep;
|
||||
uint16_t epr;
|
||||
|
||||
/* Decode and service non control endpoints interrupt */
|
||||
/* Decode and service non control endpoints interrupt */
|
||||
|
||||
epr = stm32_getreg(STM32_USB_EPR(epno));
|
||||
privep = &priv->eplist[epno];
|
||||
@ -1591,8 +1669,8 @@ static void stm32_epdone(struct stm32_usbdev_s *priv, uint8_t epno)
|
||||
|
||||
stm32_clrepctrtx(epno);
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EPINDONE), epr);
|
||||
|
||||
/* Handle write requests */
|
||||
|
||||
/* Handle write requests */
|
||||
|
||||
priv->txstatus = USB_EPR_STATTX_NAK;
|
||||
if (epno == EP0)
|
||||
@ -1607,17 +1685,17 @@ static void stm32_epdone(struct stm32_usbdev_s *priv, uint8_t epno)
|
||||
/* Set the new TX status */
|
||||
|
||||
stm32_seteptxstatus(epno, priv->txstatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_setdevaddr
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_setdevaddr(struct stm32_usbdev_s *priv, uint8_t value)
|
||||
static void stm32_setdevaddr(struct stm32_usbdev_s *priv, uint8_t value)
|
||||
{
|
||||
int epno;
|
||||
|
||||
|
||||
/* Set address in every allocated endpoint */
|
||||
|
||||
for (epno = 0; epno < STM32_NENDPOINTS; epno++)
|
||||
@ -1671,20 +1749,50 @@ static void stm32_ep0setup(struct stm32_usbdev_s *priv)
|
||||
ep0->stalled = 0;
|
||||
ep0->txbusy = 0;
|
||||
|
||||
/* Get a 32-bit PMA address and use that to get the 8-byte setup request */
|
||||
/* Check to see if called from the DATA phase of a SETUP Trasfer */
|
||||
|
||||
stm32_copyfrompma((uint8_t*)&priv->ctrl, stm32_geteprxaddr(EP0), USB_SIZEOF_CTRLREQ);
|
||||
if (priv->ep0state != EP0STATE_SETUP_READY)
|
||||
{
|
||||
/* Not the data phase */
|
||||
/* Get a 32-bit PMA address and use that to get the 8-byte setup request */
|
||||
|
||||
/* And extract the little-endian 16-bit values to host order */
|
||||
stm32_copyfrompma((uint8_t*)&priv->ctrl, stm32_geteprxaddr(EP0), USB_SIZEOF_CTRLREQ);
|
||||
|
||||
value.w = GETUINT16(priv->ctrl.value);
|
||||
index.w = GETUINT16(priv->ctrl.index);
|
||||
len.w = GETUINT16(priv->ctrl.len);
|
||||
/* And extract the little-endian 16-bit values to host order */
|
||||
|
||||
ullvdbg("SETUP: type=%02x req=%02x value=%04x index=%04x len=%04x\n",
|
||||
priv->ctrl.type, priv->ctrl.req, value.w, index.w, len.w);
|
||||
value.w = GETUINT16(priv->ctrl.value);
|
||||
index.w = GETUINT16(priv->ctrl.index);
|
||||
len.w = GETUINT16(priv->ctrl.len);
|
||||
|
||||
priv->ep0state = EP0STATE_IDLE;
|
||||
ullvdbg("SETUP: type=%02x req=%02x value=%04x index=%04x len=%04x\n",
|
||||
priv->ctrl.type, priv->ctrl.req, value.w, index.w, len.w);
|
||||
|
||||
/* Is this an setup with OUT and data of length > 0 */
|
||||
|
||||
if (USB_REQ_ISOUT(priv->ctrl.type) && len.w > 0)
|
||||
{
|
||||
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EP0SETUPOUT), len.w);
|
||||
|
||||
/* At this point priv->ctrl is the setup packet. Just ACK the
|
||||
* setup packet So we can get the data.
|
||||
*
|
||||
* REVISIT: I suspect that this should not be here?
|
||||
*/
|
||||
|
||||
stm32_epwrite(priv, ep0, response.b, 0);
|
||||
|
||||
/* Enable and Wait for the data phase. */
|
||||
|
||||
priv->rxstatus = USB_EPR_STATRX_VALID;
|
||||
priv->ep0state = EP0STATE_SETUP_OUT;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
priv->ep0state = EP0STATE_SETUP_READY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dispatch any non-standard requests */
|
||||
|
||||
@ -1740,7 +1848,7 @@ static void stm32_ep0setup(struct stm32_usbdev_s *priv)
|
||||
|
||||
if (USB_ISEPIN(index.b[LSB]))
|
||||
{
|
||||
/* IN endpoint */
|
||||
/* IN endpoint */
|
||||
|
||||
if (stm32_eptxstalled(epno))
|
||||
{
|
||||
@ -1751,7 +1859,7 @@ static void stm32_ep0setup(struct stm32_usbdev_s *priv)
|
||||
}
|
||||
else
|
||||
{
|
||||
/* OUT endpoint */
|
||||
/* OUT endpoint */
|
||||
|
||||
if (stm32_eprxstalled(epno))
|
||||
{
|
||||
@ -2094,7 +2202,7 @@ static void stm32_ep0in(struct stm32_usbdev_s *priv)
|
||||
* If so, then now is the time to set the address.
|
||||
*/
|
||||
|
||||
if (priv->ctrl.req == USB_REQ_SETADDRESS &&
|
||||
if (priv->ctrl.req == USB_REQ_SETADDRESS &&
|
||||
(priv->ctrl.type & REQRECIPIENT_MASK) ==
|
||||
(USB_REQ_TYPE_STANDARD | USB_REQ_RECIPIENT_DEVICE))
|
||||
{
|
||||
@ -2120,12 +2228,24 @@ static void stm32_ep0out(struct stm32_usbdev_s *priv)
|
||||
struct stm32_ep_s *privep = &priv->eplist[EP0];
|
||||
switch (priv->ep0state)
|
||||
{
|
||||
case EP0STATE_RDREQUEST: /* Read request in progress */
|
||||
case EP0STATE_IDLE: /* No transfer in progress */
|
||||
case EP0STATE_RDREQUEST: /* Read request in progress */
|
||||
case EP0STATE_IDLE: /* No transfer in progress */
|
||||
ret = stm32_rdrequest(priv, privep);
|
||||
priv->ep0state = ((ret == OK) ? EP0STATE_RDREQUEST : EP0STATE_IDLE);
|
||||
break;
|
||||
|
||||
case EP0STATE_SETUP_OUT: /* SETUP was waiting for data */
|
||||
ret = stm32_ep0_rdrequest(priv); /* Off load the data and run the
|
||||
* last set up command with the OUT
|
||||
* data
|
||||
*/
|
||||
priv->ep0state = EP0STATE_IDLE; /* There is no notion of reciving OUT
|
||||
* data greater then the length of
|
||||
* CONFIG_USBDEV_SETUP_MAXDATASIZE
|
||||
* so we are done
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unexpected state OR host aborted the OUT transfer before it
|
||||
* completed, STALL the endpoint in either case
|
||||
@ -2147,16 +2267,16 @@ static inline void stm32_ep0done(struct stm32_usbdev_s *priv, uint16_t istr)
|
||||
/* Initialize RX and TX status. We shouldn't have to actually look at the
|
||||
* status because the hardware is supposed to set the both RX and TX status
|
||||
* to NAK when an EP0 SETUP occurs (of course, this might not be a setup)
|
||||
*/
|
||||
*/
|
||||
|
||||
priv->rxstatus = USB_EPR_STATRX_NAK;
|
||||
priv->txstatus = USB_EPR_STATTX_NAK;
|
||||
|
||||
/* Set both RX and TX status to NAK */
|
||||
/* Set both RX and TX status to NAK */
|
||||
|
||||
stm32_seteprxstatus(EP0, USB_EPR_STATRX_NAK);
|
||||
stm32_seteptxstatus(EP0, USB_EPR_STATTX_NAK);
|
||||
|
||||
|
||||
/* Check the direction bit to determine if this the completion of an EP0
|
||||
* packet sent to or received from the host PC.
|
||||
*/
|
||||
@ -2189,7 +2309,7 @@ static inline void stm32_ep0done(struct stm32_usbdev_s *priv, uint16_t istr)
|
||||
/* SETUP is set by the hardware when the last completed
|
||||
* transaction was a control endpoint SETUP
|
||||
*/
|
||||
|
||||
|
||||
else if ((epr & USB_EPR_SETUP) != 0)
|
||||
{
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_EP0SETUPDONE), epr);
|
||||
@ -2253,7 +2373,7 @@ static inline void stm32_ep0done(struct stm32_usbdev_s *priv, uint16_t istr)
|
||||
priv->rxstatus = USB_EPR_STATRX_VALID;
|
||||
}
|
||||
|
||||
/* Now set the new TX and RX status */
|
||||
/* Now set the new TX and RX status */
|
||||
|
||||
stm32_seteprxstatus(EP0, priv->rxstatus);
|
||||
stm32_seteptxstatus(EP0, priv->txstatus);
|
||||
@ -2263,7 +2383,7 @@ static inline void stm32_ep0done(struct stm32_usbdev_s *priv, uint16_t istr)
|
||||
* Name: stm32_lptransfer
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_lptransfer(struct stm32_usbdev_s *priv)
|
||||
static void stm32_lptransfer(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
uint8_t epno;
|
||||
uint16_t istr;
|
||||
@ -2274,7 +2394,7 @@ static void stm32_lptransfer(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
stm32_putreg((uint16_t)~USB_ISTR_CTR, STM32_USB_ISTR);
|
||||
|
||||
/* Extract highest priority endpoint number */
|
||||
/* Extract highest priority endpoint number */
|
||||
|
||||
epno = (uint8_t)(istr & USB_ISTR_EPID_MASK);
|
||||
|
||||
@ -2318,8 +2438,8 @@ static int stm32_hpinterrupt(int irq, void *context)
|
||||
while ((istr & USB_ISTR_CTR) != 0)
|
||||
{
|
||||
stm32_putreg((uint16_t)~USB_ISTR_CTR, STM32_USB_ISTR);
|
||||
|
||||
/* Extract highest priority endpoint number */
|
||||
|
||||
/* Extract highest priority endpoint number */
|
||||
|
||||
epno = (uint8_t)(istr & USB_ISTR_EPID_MASK);
|
||||
|
||||
@ -2404,7 +2524,7 @@ static int stm32_lpinterrupt(int irq, void *context)
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_SUSP), 0);
|
||||
stm32_suspend(priv);
|
||||
|
||||
/* Clear of the ISTR bit must be done after setting of USB_CNTR_FSUSP */
|
||||
/* Clear of the ISTR bit must be done after setting of USB_CNTR_FSUSP */
|
||||
|
||||
stm32_putreg(~USB_ISTR_SUSP, STM32_USB_ISTR);
|
||||
}
|
||||
@ -2412,8 +2532,8 @@ static int stm32_lpinterrupt(int irq, void *context)
|
||||
if ((istr & USB_ISTR_ESOF & priv->imask) != 0)
|
||||
{
|
||||
stm32_putreg(~USB_ISTR_ESOF, STM32_USB_ISTR);
|
||||
|
||||
/* Resume handling timing is made with ESOFs */
|
||||
|
||||
/* Resume handling timing is made with ESOFs */
|
||||
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_ESOF), 0);
|
||||
stm32_esofpoll(priv);
|
||||
@ -2421,7 +2541,7 @@ static int stm32_lpinterrupt(int irq, void *context)
|
||||
|
||||
if ((istr & USB_ISTR_CTR & priv->imask) != 0)
|
||||
{
|
||||
/* Low priority endpoint correct transfer interrupt */
|
||||
/* Low priority endpoint correct transfer interrupt */
|
||||
|
||||
usbtrace(TRACE_INTDECODE(STM32_TRACEINTID_LPCTR), istr);
|
||||
stm32_lptransfer(priv);
|
||||
@ -2463,10 +2583,10 @@ stm32_setimask(struct stm32_usbdev_s *priv, uint16_t setbits, uint16_t clrbits)
|
||||
* Name: stm32_suspend
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_suspend(struct stm32_usbdev_s *priv)
|
||||
static void stm32_suspend(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
uint16_t regval;
|
||||
|
||||
|
||||
/* Notify the class driver of the suspend event */
|
||||
|
||||
if (priv->driver)
|
||||
@ -2505,16 +2625,16 @@ static void stm32_suspend(struct stm32_usbdev_s *priv)
|
||||
|
||||
/* Let the board-specific logic know that we have entered the suspend
|
||||
* state
|
||||
*/
|
||||
*/
|
||||
|
||||
stm32_usbsuspend((struct usbdev_s *)priv, false);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_initresume
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_initresume(struct stm32_usbdev_s *priv)
|
||||
static void stm32_initresume(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
uint16_t regval;
|
||||
|
||||
@ -2525,17 +2645,17 @@ static void stm32_initresume(struct stm32_usbdev_s *priv)
|
||||
|
||||
/* Clear the USB low power mode (lower power mode was not set if this is
|
||||
* a self-powered device. Also, low power mode is automatically cleared by
|
||||
* hardware when a WKUP interrupt event occurs).
|
||||
* hardware when a WKUP interrupt event occurs).
|
||||
*/
|
||||
|
||||
regval = stm32_getreg(STM32_USB_CNTR);
|
||||
regval &= (~USB_CNTR_LPMODE);
|
||||
stm32_putreg(regval, STM32_USB_CNTR);
|
||||
|
||||
/* Restore full power -- whatever that means for this particular board */
|
||||
|
||||
/* Restore full power -- whatever that means for this particular board */
|
||||
|
||||
stm32_usbsuspend((struct usbdev_s *)priv, true);
|
||||
|
||||
|
||||
/* Reset FSUSP bit and enable normal interrupt handling */
|
||||
|
||||
stm32_putreg(STM32_CNTR_SETUP, STM32_USB_CNTR);
|
||||
@ -2546,13 +2666,13 @@ static void stm32_initresume(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
CLASS_RESUME(priv->driver, &priv->usbdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_esofpoll
|
||||
****************************************************************************/
|
||||
|
||||
static void stm32_esofpoll(struct stm32_usbdev_s *priv)
|
||||
static void stm32_esofpoll(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
uint16_t regval;
|
||||
|
||||
@ -2785,7 +2905,7 @@ static int stm32_epconfigure(struct usbdev_ep_s *ep,
|
||||
if (USB_ISEPIN(desc->addr))
|
||||
{
|
||||
/* The full, logical EP number includes direction */
|
||||
|
||||
|
||||
ep->eplog = USB_EPIN(epno);
|
||||
|
||||
/* Set up TX; disable RX */
|
||||
@ -3068,7 +3188,7 @@ static int stm32_epstall(struct usbdev_ep_s *ep, bool resume)
|
||||
/* Get status of the endpoint; stall the request if the endpoint is
|
||||
* disabled
|
||||
*/
|
||||
|
||||
|
||||
if (USB_ISEPIN(ep->eplog))
|
||||
{
|
||||
status = stm32_geteptxstatus(epno);
|
||||
@ -3101,7 +3221,7 @@ static int stm32_epstall(struct usbdev_ep_s *ep, bool resume)
|
||||
|
||||
if (USB_ISEPIN(ep->eplog))
|
||||
{
|
||||
/* IN endpoint */
|
||||
/* IN endpoint */
|
||||
|
||||
if (stm32_eptxstalled(epno))
|
||||
{
|
||||
@ -3126,7 +3246,7 @@ static int stm32_epstall(struct usbdev_ep_s *ep, bool resume)
|
||||
}
|
||||
else
|
||||
{
|
||||
/* OUT endpoint */
|
||||
/* OUT endpoint */
|
||||
|
||||
if (stm32_eprxstalled(epno))
|
||||
{
|
||||
@ -3144,7 +3264,7 @@ static int stm32_epstall(struct usbdev_ep_s *ep, bool resume)
|
||||
priv->rxstatus = USB_EPR_STATRX_VALID;
|
||||
stm32_seteprxstatus(epno, USB_EPR_STATRX_VALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle the stall condition */
|
||||
@ -3156,14 +3276,14 @@ static int stm32_epstall(struct usbdev_ep_s *ep, bool resume)
|
||||
|
||||
if (USB_ISEPIN(ep->eplog))
|
||||
{
|
||||
/* IN endpoint */
|
||||
/* IN endpoint */
|
||||
|
||||
priv->txstatus = USB_EPR_STATTX_STALL;
|
||||
stm32_seteptxstatus(epno, USB_EPR_STATTX_STALL);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* OUT endpoint */
|
||||
/* OUT endpoint */
|
||||
|
||||
priv->rxstatus = USB_EPR_STATRX_STALL;
|
||||
stm32_seteprxstatus(epno, USB_EPR_STATRX_STALL);
|
||||
@ -3426,7 +3546,7 @@ static void stm32_reset(struct stm32_usbdev_s *priv)
|
||||
|
||||
stm32_hwreset(priv);
|
||||
priv->usbdev.speed = USB_SPEED_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_hwreset
|
||||
@ -3487,10 +3607,10 @@ static void stm32_hwsetup(struct stm32_usbdev_s *priv)
|
||||
|
||||
/* Disconnect the device / disable the pull-up. We don't want the
|
||||
* host to enumerate us until the class driver is registered.
|
||||
*/
|
||||
*/
|
||||
|
||||
stm32_usbpullup(&priv->usbdev, false);
|
||||
|
||||
|
||||
/* Initialize the device state structure. NOTE: many fields
|
||||
* have the initial value of zero and, hence, are not explicitly
|
||||
* initialized here.
|
||||
@ -3560,18 +3680,18 @@ static void stm32_hwshutdown(struct stm32_usbdev_s *priv)
|
||||
{
|
||||
priv->usbdev.speed = USB_SPEED_UNKNOWN;
|
||||
|
||||
/* Disable all interrupts and force the USB controller into reset */
|
||||
/* Disable all interrupts and force the USB controller into reset */
|
||||
|
||||
stm32_putreg(USB_CNTR_FRES, STM32_USB_CNTR);
|
||||
|
||||
/* Clear any pending interrupts */
|
||||
/* Clear any pending interrupts */
|
||||
|
||||
stm32_putreg(0, STM32_USB_ISTR);
|
||||
|
||||
/* Disconnect the device / disable the pull-up */
|
||||
/* Disconnect the device / disable the pull-up */
|
||||
|
||||
stm32_usbpullup(&priv->usbdev, false);
|
||||
|
||||
|
||||
/* Power down the USB controller */
|
||||
|
||||
stm32_putreg(USB_CNTR_FRES|USB_CNTR_PDWN, STM32_USB_CNTR);
|
||||
@ -3592,7 +3712,7 @@ static void stm32_hwshutdown(struct stm32_usbdev_s *priv)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void up_usbinitialize(void)
|
||||
void up_usbinitialize(void)
|
||||
{
|
||||
/* For now there is only one USB controller, but we will always refer to
|
||||
* it using a pointer to make any future ports to multiple USB controllers
|
||||
@ -3652,7 +3772,7 @@ void up_usbinitialize(void)
|
||||
|
||||
errout:
|
||||
up_usbuninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: up_usbuninitialize
|
||||
@ -3666,7 +3786,7 @@ errout:
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void up_usbuninitialize(void)
|
||||
void up_usbuninitialize(void)
|
||||
{
|
||||
/* For now there is only one USB controller, but we will always refer to
|
||||
* it using a pointer to make any future ports to multiple USB controllers
|
||||
|
Loading…
Reference in New Issue
Block a user