Commit 1e0c3bc6 authored by Dean Camera's avatar Dean Camera
Browse files

Namespace out the internal endpoint globals in the XMEGA architecture.

parent fe87b8a2
...@@ -39,11 +39,11 @@ ...@@ -39,11 +39,11 @@
uint8_t USB_Device_ControlEndpointSize = ENDPOINT_CONTROLEP_DEFAULT_SIZE; uint8_t USB_Device_ControlEndpointSize = ENDPOINT_CONTROLEP_DEFAULT_SIZE;
#endif #endif
Endpoint_AuxData_t Endpoint_AuxData[ENDPOINT_DETAILS_MAXEP * 2]; Endpoint_FIFOPair_t USB_Endpoint_FIFOs[ENDPOINT_DETAILS_MAXEP];
volatile uint8_t Endpoint_SelectedEndpoint; volatile uint8_t USB_Endpoint_SelectedEndpoint;
volatile USB_EP_t* Endpoint_SelectedEndpointHandle; volatile USB_EP_t* USB_Endpoint_SelectedHandle;
volatile Endpoint_AuxData_t* Endpoint_SelectedEndpointAux; volatile Endpoint_FIFO_t* USB_Endpoint_SelectedFIFO;
bool Endpoint_ConfigureEndpoint_PRV(const uint8_t Number, bool Endpoint_ConfigureEndpoint_PRV(const uint8_t Number,
const uint8_t Direction, const uint8_t Direction,
...@@ -52,15 +52,14 @@ bool Endpoint_ConfigureEndpoint_PRV(const uint8_t Number, ...@@ -52,15 +52,14 @@ bool Endpoint_ConfigureEndpoint_PRV(const uint8_t Number,
{ {
Endpoint_SelectEndpoint(Number | Direction); Endpoint_SelectEndpoint(Number | Direction);
Endpoint_SelectedEndpointHandle->CTRL = 0; USB_Endpoint_SelectedHandle->CTRL = 0;
Endpoint_SelectedEndpointHandle->STATUS = (Direction == ENDPOINT_DIR_IN) ? USB_EP_BUSNACK0_bm : 0; USB_Endpoint_SelectedHandle->STATUS = (Direction == ENDPOINT_DIR_IN) ? (USB_EP_BUSNACK0_bm | USB_EP_TRNCOMPL0_bm) : USB_EP_BUSNACK0_bm;
Endpoint_SelectedEndpointHandle->CTRL = Config; USB_Endpoint_SelectedHandle->CTRL = Config;
Endpoint_SelectedEndpointHandle->CNT = 0; USB_Endpoint_SelectedHandle->CNT = 0;
Endpoint_SelectedEndpointHandle->DATAPTR = (intptr_t)&Endpoint_SelectedEndpointAux->Data; USB_Endpoint_SelectedHandle->DATAPTR = (intptr_t)&USB_Endpoint_SelectedFIFO->Data[0];
Endpoint_SelectedEndpointAux->BankSize = Size; USB_Endpoint_SelectedFIFO->Length = (Direction == ENDPOINT_DIR_IN) ? Size : 0;
Endpoint_SelectedEndpointAux->FIFOLength = (Direction == ENDPOINT_DIR_IN) ? Size : 0; USB_Endpoint_SelectedFIFO->Position = 0;
Endpoint_SelectedEndpointAux->FIFOPosition = 0;
return true; return true;
} }
......
...@@ -99,17 +99,23 @@ ...@@ -99,17 +99,23 @@
/* Type Defines: */ /* Type Defines: */
typedef struct typedef struct
{ {
uint8_t Data[64]; uint8_t Data[64];
uint8_t BankSize;
uint8_t FIFOLength; uint8_t Length;
uint8_t FIFOPosition; uint8_t Position;
} Endpoint_AuxData_t; } Endpoint_FIFO_t;
typedef struct
{
Endpoint_FIFO_t OUT;
Endpoint_FIFO_t IN;
} Endpoint_FIFOPair_t;
/* External Variables: */ /* External Variables: */
extern Endpoint_AuxData_t Endpoint_AuxData[ENDPOINT_DETAILS_MAXEP * 2]; extern Endpoint_FIFOPair_t USB_Endpoint_FIFOs[ENDPOINT_DETAILS_MAXEP];
extern volatile uint8_t Endpoint_SelectedEndpoint; extern volatile uint8_t USB_Endpoint_SelectedEndpoint;
extern volatile USB_EP_t* Endpoint_SelectedEndpointHandle; extern volatile USB_EP_t* USB_Endpoint_SelectedHandle;
extern volatile Endpoint_AuxData_t* Endpoint_SelectedEndpointAux; extern volatile Endpoint_FIFO_t* USB_Endpoint_SelectedFIFO;
/* Inline Functions: */ /* Inline Functions: */
static inline uint8_t Endpoint_BytesToEPSizeMask(const uint16_t Bytes) ATTR_WARN_UNUSED_RESULT ATTR_CONST static inline uint8_t Endpoint_BytesToEPSizeMask(const uint16_t Bytes) ATTR_WARN_UNUSED_RESULT ATTR_CONST
...@@ -224,17 +230,21 @@ ...@@ -224,17 +230,21 @@
* *
* \param[in] EndpointNumber Endpoint number to select. * \param[in] EndpointNumber Endpoint number to select.
*/ */
static inline void Endpoint_SelectEndpoint(const uint8_t EndpointNumber) ATTR_ALWAYS_INLINE; static inline void Endpoint_SelectEndpoint(const uint8_t EndpointNumber);
static inline void Endpoint_SelectEndpoint(const uint8_t EndpointNumber) static inline void Endpoint_SelectEndpoint(const uint8_t EndpointNumber)
{ {
uint8_t EPTableIndex = ((EndpointNumber & ENDPOINT_EPNUM_MASK) << 1) | USB_Endpoint_SelectedEndpoint = EndpointNumber;
((EndpointNumber & ENDPOINT_DIR_IN) ? 0x01 : 0);
if (EndpointNumber & ENDPOINT_DIR_IN)
Endpoint_SelectedEndpoint = EndpointNumber; {
Endpoint_SelectedEndpointAux = &Endpoint_AuxData[EPTableIndex]; USB_Endpoint_SelectedFIFO = &USB_Endpoint_FIFOs[EndpointNumber & ENDPOINT_EPNUM_MASK].IN;
Endpoint_SelectedEndpointHandle = (EndpointNumber & ENDPOINT_DIR_IN) ? USB_Endpoint_SelectedHandle = &USB_EndpointTable.Endpoints[EndpointNumber & ENDPOINT_EPNUM_MASK].IN;
&USB_EndpointTable.Endpoints[EndpointNumber & ENDPOINT_EPNUM_MASK].IN : }
&USB_EndpointTable.Endpoints[EndpointNumber & ENDPOINT_EPNUM_MASK].OUT; else
{
USB_Endpoint_SelectedFIFO = &USB_Endpoint_FIFOs[EndpointNumber & ENDPOINT_EPNUM_MASK].OUT;
USB_Endpoint_SelectedHandle = &USB_EndpointTable.Endpoints[EndpointNumber & ENDPOINT_EPNUM_MASK].OUT;
}
} }
/** Configures the specified endpoint number with the given endpoint type, direction, bank size /** Configures the specified endpoint number with the given endpoint type, direction, bank size
...@@ -260,20 +270,11 @@ ...@@ -260,20 +270,11 @@
* More banks uses more USB DPRAM, but offers better performance. Isochronous type * More banks uses more USB DPRAM, but offers better performance. Isochronous type
* endpoints <b>must</b> have at least two banks. * endpoints <b>must</b> have at least two banks.
* *
* \note When the \c ORDERED_EP_CONFIG compile time option is used, Endpoints <b>must</b> be configured in
* ascending order, or bank corruption will occur.
* \n\n
*
* \note Different endpoints may have different maximum packet sizes based on the endpoint's index - refer to
* the chosen microcontroller model's datasheet to determine the maximum bank size for each endpoint.
* \n\n
*
* \note The default control endpoint should not be manually configured by the user application, as * \note The default control endpoint should not be manually configured by the user application, as
* it is automatically configured by the library internally. * it is automatically configured by the library internally.
* \n\n * \n\n
* *
* \note This routine will automatically select the specified endpoint upon success. Upon failure, the endpoint * \note This routine will automatically select the specified endpoint.
* which failed to reconfigure correctly will be selected.
* *
* \return Boolean \c true if the configuration succeeded, \c false otherwise. * \return Boolean \c true if the configuration succeeded, \c false otherwise.
*/ */
...@@ -321,7 +322,7 @@ ...@@ -321,7 +322,7 @@
static inline uint16_t Endpoint_BytesInEndpoint(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline uint16_t Endpoint_BytesInEndpoint(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline uint16_t Endpoint_BytesInEndpoint(void) static inline uint16_t Endpoint_BytesInEndpoint(void)
{ {
return Endpoint_SelectedEndpointAux->FIFOPosition; return USB_Endpoint_SelectedFIFO->Position;
} }
/** Get the endpoint address of the currently selected endpoint. This is typically used to save /** Get the endpoint address of the currently selected endpoint. This is typically used to save
...@@ -333,7 +334,7 @@ ...@@ -333,7 +334,7 @@
static inline uint8_t Endpoint_GetCurrentEndpoint(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline uint8_t Endpoint_GetCurrentEndpoint(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline uint8_t Endpoint_GetCurrentEndpoint(void) static inline uint8_t Endpoint_GetCurrentEndpoint(void)
{ {
return Endpoint_SelectedEndpoint; return USB_Endpoint_SelectedEndpoint;
} }
/** Resets the endpoint bank FIFO. This clears all the endpoint banks and resets the USB controller's /** Resets the endpoint bank FIFO. This clears all the endpoint banks and resets the USB controller's
...@@ -344,7 +345,7 @@ ...@@ -344,7 +345,7 @@
static inline void Endpoint_ResetEndpoint(const uint8_t EndpointNumber) ATTR_ALWAYS_INLINE; static inline void Endpoint_ResetEndpoint(const uint8_t EndpointNumber) ATTR_ALWAYS_INLINE;
static inline void Endpoint_ResetEndpoint(const uint8_t EndpointNumber) static inline void Endpoint_ResetEndpoint(const uint8_t EndpointNumber)
{ {
Endpoint_SelectedEndpointAux->FIFOPosition = 0; USB_Endpoint_SelectedFIFO->Position = 0;
} }
/** Determines if the currently selected endpoint is enabled, but not necessarily configured. /** Determines if the currently selected endpoint is enabled, but not necessarily configured.
...@@ -383,7 +384,7 @@ ...@@ -383,7 +384,7 @@
static inline bool Endpoint_IsReadWriteAllowed(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline bool Endpoint_IsReadWriteAllowed(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline bool Endpoint_IsReadWriteAllowed(void) static inline bool Endpoint_IsReadWriteAllowed(void)
{ {
return (Endpoint_SelectedEndpointAux->FIFOPosition < Endpoint_SelectedEndpointAux->FIFOLength); return (USB_Endpoint_SelectedFIFO->Position < USB_Endpoint_SelectedFIFO->Length);
} }
/** Determines if the currently selected endpoint is configured. /** Determines if the currently selected endpoint is configured.
...@@ -393,7 +394,7 @@ ...@@ -393,7 +394,7 @@
static inline bool Endpoint_IsConfigured(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline bool Endpoint_IsConfigured(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline bool Endpoint_IsConfigured(void) static inline bool Endpoint_IsConfigured(void)
{ {
return ((Endpoint_SelectedEndpointHandle->CTRL & USB_EP_TYPE_gm) ? true : false); return ((USB_Endpoint_SelectedHandle->CTRL & USB_EP_TYPE_gm) ? true : false);
} }
/** Returns a mask indicating which INTERRUPT type endpoints have interrupted - i.e. their /** Returns a mask indicating which INTERRUPT type endpoints have interrupted - i.e. their
...@@ -430,7 +431,9 @@ ...@@ -430,7 +431,9 @@
static inline bool Endpoint_IsINReady(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline bool Endpoint_IsINReady(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline bool Endpoint_IsINReady(void) static inline bool Endpoint_IsINReady(void)
{ {
return ((Endpoint_SelectedEndpointHandle->STATUS & USB_EP_TRNCOMPL0_bm) ? true : false); Endpoint_SelectEndpoint(USB_Endpoint_SelectedEndpoint | ENDPOINT_DIR_IN);
return ((USB_Endpoint_SelectedHandle->STATUS & USB_EP_TRNCOMPL0_bm) ? true : false);
} }
/** Determines if the selected OUT endpoint has received new packet from the host. /** Determines if the selected OUT endpoint has received new packet from the host.
...@@ -442,9 +445,11 @@ ...@@ -442,9 +445,11 @@
static inline bool Endpoint_IsOUTReceived(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline bool Endpoint_IsOUTReceived(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline bool Endpoint_IsOUTReceived(void) static inline bool Endpoint_IsOUTReceived(void)
{ {
if (Endpoint_SelectedEndpointHandle->STATUS & USB_EP_TRNCOMPL0_bm) Endpoint_SelectEndpoint(USB_Endpoint_SelectedEndpoint & ~ENDPOINT_DIR_IN);
if (USB_Endpoint_SelectedHandle->STATUS & USB_EP_TRNCOMPL0_bm)
{ {
Endpoint_SelectedEndpointAux->FIFOLength = Endpoint_SelectedEndpointHandle->CNT; USB_Endpoint_SelectedFIFO->Length = USB_Endpoint_SelectedHandle->CNT;
return true; return true;
} }
...@@ -460,7 +465,15 @@ ...@@ -460,7 +465,15 @@
static inline bool Endpoint_IsSETUPReceived(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline bool Endpoint_IsSETUPReceived(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline bool Endpoint_IsSETUPReceived(void) static inline bool Endpoint_IsSETUPReceived(void)
{ {
return ((Endpoint_SelectedEndpointHandle->STATUS & USB_EP_SETUP_bm) ? true : false); Endpoint_SelectEndpoint(USB_Endpoint_SelectedEndpoint & ~ENDPOINT_DIR_IN);
if (USB_Endpoint_SelectedHandle->STATUS & USB_EP_SETUP_bm)
{
USB_Endpoint_SelectedFIFO->Length = USB_Endpoint_SelectedHandle->CNT;
return true;
}
return false;
} }
/** Clears a received SETUP packet on the currently selected CONTROL type endpoint, freeing up the /** Clears a received SETUP packet on the currently selected CONTROL type endpoint, freeing up the
...@@ -473,9 +486,8 @@ ...@@ -473,9 +486,8 @@
static inline void Endpoint_ClearSETUP(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_ClearSETUP(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_ClearSETUP(void) static inline void Endpoint_ClearSETUP(void)
{ {
Endpoint_SelectedEndpointHandle->STATUS &= ~(USB_EP_SETUP_bm | USB_EP_BUSNACK0_bm); USB_Endpoint_SelectedHandle->STATUS &= ~(USB_EP_SETUP_bm | USB_EP_BUSNACK0_bm);
USB_Endpoint_SelectedFIFO->Position = 0;
Endpoint_SelectedEndpointAux->FIFOPosition = 0;
} }
/** Sends an IN packet to the host on the currently selected endpoint, freeing up the endpoint for the /** Sends an IN packet to the host on the currently selected endpoint, freeing up the endpoint for the
...@@ -486,10 +498,9 @@ ...@@ -486,10 +498,9 @@
static inline void Endpoint_ClearIN(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_ClearIN(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_ClearIN(void) static inline void Endpoint_ClearIN(void)
{ {
Endpoint_SelectedEndpointHandle->CNT = Endpoint_SelectedEndpointAux->FIFOPosition; USB_Endpoint_SelectedHandle->CNT = USB_Endpoint_SelectedFIFO->Position;
Endpoint_SelectedEndpointHandle->STATUS &= ~(USB_EP_TRNCOMPL0_bm | USB_EP_BUSNACK0_bm); USB_Endpoint_SelectedHandle->STATUS &= ~(USB_EP_TRNCOMPL0_bm | USB_EP_BUSNACK0_bm | USB_EP_OVF_bm);
USB_Endpoint_SelectedFIFO->Position = 0;
Endpoint_SelectedEndpointAux->FIFOPosition = 0;
} }
/** Acknowledges an OUT packet to the host on the currently selected endpoint, freeing up the endpoint /** Acknowledges an OUT packet to the host on the currently selected endpoint, freeing up the endpoint
...@@ -500,9 +511,8 @@ ...@@ -500,9 +511,8 @@
static inline void Endpoint_ClearOUT(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_ClearOUT(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_ClearOUT(void) static inline void Endpoint_ClearOUT(void)
{ {
Endpoint_SelectedEndpointHandle->STATUS &= ~(USB_EP_TRNCOMPL0_bm | USB_EP_BUSNACK0_bm); USB_Endpoint_SelectedHandle->STATUS &= ~(USB_EP_TRNCOMPL0_bm | USB_EP_BUSNACK0_bm | USB_EP_OVF_bm);
USB_Endpoint_SelectedFIFO->Position = 0;
Endpoint_SelectedEndpointAux->FIFOPosition = 0;
} }
/** Stalls the current endpoint, indicating to the host that a logical problem occurred with the /** Stalls the current endpoint, indicating to the host that a logical problem occurred with the
...@@ -519,7 +529,7 @@ ...@@ -519,7 +529,7 @@
static inline void Endpoint_StallTransaction(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_StallTransaction(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_StallTransaction(void) static inline void Endpoint_StallTransaction(void)
{ {
Endpoint_SelectedEndpointHandle->CTRL |= USB_EP_STALL_bm; USB_Endpoint_SelectedHandle->CTRL |= USB_EP_STALL_bm;
} }
/** Clears the STALL condition on the currently selected endpoint. /** Clears the STALL condition on the currently selected endpoint.
...@@ -529,7 +539,7 @@ ...@@ -529,7 +539,7 @@
static inline void Endpoint_ClearStall(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_ClearStall(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_ClearStall(void) static inline void Endpoint_ClearStall(void)
{ {
Endpoint_SelectedEndpointHandle->CTRL &= ~USB_EP_STALL_bm; USB_Endpoint_SelectedHandle->CTRL &= ~USB_EP_STALL_bm;
} }
/** Determines if the currently selected endpoint is stalled, false otherwise. /** Determines if the currently selected endpoint is stalled, false otherwise.
...@@ -541,14 +551,14 @@ ...@@ -541,14 +551,14 @@
static inline bool Endpoint_IsStalled(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline bool Endpoint_IsStalled(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline bool Endpoint_IsStalled(void) static inline bool Endpoint_IsStalled(void)
{ {
return ((Endpoint_SelectedEndpointHandle->CTRL & USB_EP_STALL_bm) ? true : false); return ((USB_Endpoint_SelectedHandle->CTRL & USB_EP_STALL_bm) ? true : false);
} }
/** Resets the data toggle of the currently selected endpoint. */ /** Resets the data toggle of the currently selected endpoint. */
static inline void Endpoint_ResetDataToggle(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_ResetDataToggle(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_ResetDataToggle(void) static inline void Endpoint_ResetDataToggle(void)
{ {
Endpoint_SelectedEndpointHandle->STATUS &= ~USB_EP_TOGGLE_bm; USB_Endpoint_SelectedHandle->STATUS &= ~USB_EP_TOGGLE_bm;
} }
/** Determines the currently selected endpoint's direction. /** Determines the currently selected endpoint's direction.
...@@ -558,7 +568,7 @@ ...@@ -558,7 +568,7 @@
static inline uint8_t Endpoint_GetEndpointDirection(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline uint8_t Endpoint_GetEndpointDirection(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline uint8_t Endpoint_GetEndpointDirection(void) static inline uint8_t Endpoint_GetEndpointDirection(void)
{ {
return ((Endpoint_SelectedEndpoint & ENDPOINT_DIR_IN) ? true : false); return ((USB_Endpoint_SelectedEndpoint & ENDPOINT_DIR_IN) ? true : false);
} }
/** Sets the direction of the currently selected endpoint. /** Sets the direction of the currently selected endpoint.
...@@ -580,7 +590,7 @@ ...@@ -580,7 +590,7 @@
static inline uint8_t Endpoint_Read_8(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE; static inline uint8_t Endpoint_Read_8(void) ATTR_WARN_UNUSED_RESULT ATTR_ALWAYS_INLINE;
static inline uint8_t Endpoint_Read_8(void) static inline uint8_t Endpoint_Read_8(void)
{ {
return Endpoint_SelectedEndpointAux->Data[Endpoint_SelectedEndpointAux->FIFOPosition++]; return USB_Endpoint_SelectedFIFO->Data[USB_Endpoint_SelectedFIFO->Position++];
} }
/** Writes one byte to the currently selected endpoint's bank, for IN direction endpoints. /** Writes one byte to the currently selected endpoint's bank, for IN direction endpoints.
...@@ -592,7 +602,7 @@ ...@@ -592,7 +602,7 @@
static inline void Endpoint_Write_8(const uint8_t Data) ATTR_ALWAYS_INLINE; static inline void Endpoint_Write_8(const uint8_t Data) ATTR_ALWAYS_INLINE;
static inline void Endpoint_Write_8(const uint8_t Data) static inline void Endpoint_Write_8(const uint8_t Data)
{ {
Endpoint_SelectedEndpointAux->Data[Endpoint_SelectedEndpointAux->FIFOPosition++] = Data; USB_Endpoint_SelectedFIFO->Data[USB_Endpoint_SelectedFIFO->Position++] = Data;
} }
/** Discards one byte from the currently selected endpoint's bank, for OUT direction endpoints. /** Discards one byte from the currently selected endpoint's bank, for OUT direction endpoints.
...@@ -602,7 +612,7 @@ ...@@ -602,7 +612,7 @@
static inline void Endpoint_Discard_8(void) ATTR_ALWAYS_INLINE; static inline void Endpoint_Discard_8(void) ATTR_ALWAYS_INLINE;
static inline void Endpoint_Discard_8(void) static inline void Endpoint_Discard_8(void)
{ {
Endpoint_SelectedEndpointAux->FIFOPosition++; USB_Endpoint_SelectedFIFO->Position++;
} }
/** Reads two bytes from the currently selected endpoint's bank in little endian format, for OUT /** Reads two bytes from the currently selected endpoint's bank in little endian format, for OUT
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment