Commit 43c47353 authored by Dean Camera's avatar Dean Camera
Browse files

Renamed the PRNT_Host_SendString(), CDC_Host_SendString() and...

Renamed the PRNT_Host_SendString(), CDC_Host_SendString() and CDC_Device_SendString() functions to *_SendData(), and added new versions of the *_SendString() routines that expect a null terminated string instead.

Added new Serial_SendData() function to the Serial driver.
parent afd828c0
...@@ -156,7 +156,7 @@ void CheckJoystickMovement(void) ...@@ -156,7 +156,7 @@ void CheckJoystickMovement(void)
{ {
ActionSent = true; ActionSent = true;
CDC_Device_SendString(&VirtualSerial1_CDC_Interface, ReportString, strlen(ReportString)); CDC_Device_SendString(&VirtualSerial1_CDC_Interface, ReportString);
} }
} }
......
...@@ -134,7 +134,7 @@ void CheckJoystickMovement(void) ...@@ -134,7 +134,7 @@ void CheckJoystickMovement(void)
fputs(ReportString, &USBSerialStream); fputs(ReportString, &USBSerialStream);
/* Alternatively, without the stream: */ /* Alternatively, without the stream: */
// CDC_Device_SendString(&VirtualSerial_CDC_Interface, ReportString, strlen(ReportString)); // CDC_Device_SendString(&VirtualSerial_CDC_Interface, ReportString);
} }
} }
......
...@@ -145,7 +145,7 @@ void CheckJoystickMovement(void) ...@@ -145,7 +145,7 @@ void CheckJoystickMovement(void)
{ {
ActionSent = true; ActionSent = true;
CDC_Device_SendString(&VirtualSerial_CDC_Interface, ReportString, strlen(ReportString)); CDC_Device_SendString(&VirtualSerial_CDC_Interface, ReportString);
} }
} }
......
...@@ -134,7 +134,7 @@ int main(void) ...@@ -134,7 +134,7 @@ int main(void)
printf_P(PSTR("Sending Test Page (%d bytes)...\r\n"), TestPageLength); printf_P(PSTR("Sending Test Page (%d bytes)...\r\n"), TestPageLength);
if (PRNT_Host_SendString(&Printer_PRNT_Interface, &TestPageData, TestPageLength) != PIPE_RWSTREAM_NoError) if (PRNT_Host_SendData(&Printer_PRNT_Interface, &TestPageData, TestPageLength) != PIPE_RWSTREAM_NoError)
{ {
puts_P(PSTR("Error Sending Page Data.\r\n")); puts_P(PSTR("Error Sending Page Data.\r\n"));
LEDs_SetAllLEDs(LEDMASK_USB_ERROR); LEDs_SetAllLEDs(LEDMASK_USB_ERROR);
......
...@@ -81,3 +81,8 @@ void Serial_SendString(const char* StringPtr) ...@@ -81,3 +81,8 @@ void Serial_SendString(const char* StringPtr)
} }
} }
void Serial_SendData(const uint8_t* Buffer, uint16_t Length)
{
while (Length--)
Serial_SendByte(*(Buffer++));
}
...@@ -117,6 +117,13 @@ ...@@ -117,6 +117,13 @@
*/ */
void Serial_SendString(const char* StringPtr) ATTR_NON_NULL_PTR_ARG(1); void Serial_SendString(const char* StringPtr) ATTR_NON_NULL_PTR_ARG(1);
/** Transmits a given buffer located in SRAM memory through the USART.
*
* \param[in] Buffer Pointer to a buffer containing the data to send.
* \param[in] Length Length of the data to send, in bytes.
*/
void Serial_SendData(const uint8_t* Buffer, uint16_t Length) ATTR_NON_NULL_PTR_ARG(1);
/* Inline Functions: */ /* Inline Functions: */
/** Initializes the USART, ready for serial data transmission and reception. This initializes the interface to /** Initializes the USART, ready for serial data transmission and reception. This initializes the interface to
* standard 8-bit, no parity, 1 stop bit settings suitable for most applications. * standard 8-bit, no parity, 1 stop bit settings suitable for most applications.
......
...@@ -154,14 +154,24 @@ void CDC_Device_USBTask(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo) ...@@ -154,14 +154,24 @@ void CDC_Device_USBTask(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo)
} }
uint8_t CDC_Device_SendString(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, uint8_t CDC_Device_SendString(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo,
const char* const Data, const char* const String)
{
if ((USB_DeviceState != DEVICE_STATE_Configured) || !(CDCInterfaceInfo->State.LineEncoding.BaudRateBPS))
return ENDPOINT_RWSTREAM_DeviceDisconnected;
Endpoint_SelectEndpoint(CDCInterfaceInfo->Config.DataINEndpointNumber);
return Endpoint_Write_Stream_LE(String, strlen(String), NULL);
}
uint8_t CDC_Device_SendData(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo,
const char* const Buffer,
const uint16_t Length) const uint16_t Length)
{ {
if ((USB_DeviceState != DEVICE_STATE_Configured) || !(CDCInterfaceInfo->State.LineEncoding.BaudRateBPS)) if ((USB_DeviceState != DEVICE_STATE_Configured) || !(CDCInterfaceInfo->State.LineEncoding.BaudRateBPS))
return ENDPOINT_RWSTREAM_DeviceDisconnected; return ENDPOINT_RWSTREAM_DeviceDisconnected;
Endpoint_SelectEndpoint(CDCInterfaceInfo->Config.DataINEndpointNumber); Endpoint_SelectEndpoint(CDCInterfaceInfo->Config.DataINEndpointNumber);
return Endpoint_Write_Stream_LE(Data, Length, NULL); return Endpoint_Write_Stream_LE(Buffer, Length, NULL);
} }
uint8_t CDC_Device_SendByte(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, uint8_t CDC_Device_SendByte(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo,
......
...@@ -194,24 +194,40 @@ ...@@ -194,24 +194,40 @@
void EVENT_CDC_Device_BreakSent(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, void EVENT_CDC_Device_BreakSent(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo,
const uint8_t Duration) ATTR_NON_NULL_PTR_ARG(1); const uint8_t Duration) ATTR_NON_NULL_PTR_ARG(1);
/** Sends a given string to the attached USB host, if connected. If a host is not connected when the function is called, the /** Sends a given data buffer to the attached USB host, if connected. If a host is not connected when the function is
* string is discarded. Bytes will be queued for transmission to the host until either the endpoint bank becomes full, or the * called, the string is discarded. Bytes will be queued for transmission to the host until either the endpoint bank
* \ref CDC_Device_Flush() function is called to flush the pending data to the host. This allows for multiple bytes to be * becomes full, or the \ref CDC_Device_Flush() function is called to flush the pending data to the host. This allows
* packed into a single endpoint packet, increasing data throughput. * for multiple bytes to be packed into a single endpoint packet, increasing data throughput.
* *
* \pre This function must only be called when the Device state machine is in the \ref DEVICE_STATE_Configured state or * \pre This function must only be called when the Device state machine is in the \ref DEVICE_STATE_Configured state or
* the call will fail. * the call will fail.
* *
* \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state. * \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state.
* \param[in] Data Pointer to the string to send to the host. * \param[in] Buffer Pointer to a buffer containing the data to send to the device.
* \param[in] Length Size in bytes of the string to send to the host. * \param[in] Length Length of the data to send to the host.
* *
* \return A value from the \ref Endpoint_Stream_RW_ErrorCodes_t enum. * \return A value from the \ref Endpoint_Stream_RW_ErrorCodes_t enum.
*/ */
uint8_t CDC_Device_SendString(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, uint8_t CDC_Device_SendData(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo,
const char* const Data, const char* const Buffer,
const uint16_t Length) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2); const uint16_t Length) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2);
/** Sends a given null terminated string to the attached USB host, if connected. If a host is not connected when
* the function is called, the string is discarded. Bytes will be queued for transmission to the host until either
* the endpoint bank becomes full, or the \ref CDC_Device_Flush() function is called to flush the pending data to
* the host. This allows for multiple bytes to be packed into a single endpoint packet, increasing data throughput.
*
* \pre This function must only be called when the Device state machine is in the \ref DEVICE_STATE_Configured state or
* the call will fail.
*
* \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state.
* \param[in] String Pointer to the null terminated string to send to the host.
*
* \return A value from the \ref Endpoint_Stream_RW_ErrorCodes_t enum.
*/
uint8_t CDC_Device_SendString(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo,
const char* const String) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2);
/** Sends a given byte to the attached USB host, if connected. If a host is not connected when the function is called, the /** Sends a given byte to the attached USB host, if connected. If a host is not connected when the function is called, the
* byte is discarded. Bytes will be queued for transmission to the host until either the endpoint bank becomes full, or the * byte is discarded. Bytes will be queued for transmission to the host until either the endpoint bank becomes full, or the
* \ref CDC_Device_Flush() function is called to flush the pending data to the host. This allows for multiple bytes to be * \ref CDC_Device_Flush() function is called to flush the pending data to the host. This allows for multiple bytes to be
......
...@@ -311,8 +311,8 @@ uint8_t CDC_Host_SendBreak(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo, ...@@ -311,8 +311,8 @@ uint8_t CDC_Host_SendBreak(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
return USB_Host_SendControlRequest(NULL); return USB_Host_SendControlRequest(NULL);
} }
uint8_t CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo, uint8_t CDC_Host_SendData(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
const char* const Data, const uint8_t* const Buffer,
const uint16_t Length) const uint16_t Length)
{ {
if ((USB_HostState != HOST_STATE_Configured) || !(CDCInterfaceInfo->State.IsActive)) if ((USB_HostState != HOST_STATE_Configured) || !(CDCInterfaceInfo->State.IsActive))
...@@ -323,7 +323,24 @@ uint8_t CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo, ...@@ -323,7 +323,24 @@ uint8_t CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
Pipe_SelectPipe(CDCInterfaceInfo->Config.DataOUTPipeNumber); Pipe_SelectPipe(CDCInterfaceInfo->Config.DataOUTPipeNumber);
Pipe_Unfreeze(); Pipe_Unfreeze();
ErrorCode = Pipe_Write_Stream_LE(Data, Length, NULL); ErrorCode = Pipe_Write_Stream_LE(Buffer, Length, NULL);
Pipe_Freeze();
return ErrorCode;
}
uint8_t CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
const char* const String)
{
if ((USB_HostState != HOST_STATE_Configured) || !(CDCInterfaceInfo->State.IsActive))
return PIPE_READYWAIT_DeviceDisconnected;
uint8_t ErrorCode;
Pipe_SelectPipe(CDCInterfaceInfo->Config.DataOUTPipeNumber);
Pipe_Unfreeze();
ErrorCode = Pipe_Write_Stream_LE(String, strlen(String), NULL);
Pipe_Freeze(); Pipe_Freeze();
return ErrorCode; return ErrorCode;
......
...@@ -197,23 +197,39 @@ ...@@ -197,23 +197,39 @@
uint8_t CDC_Host_SendBreak(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo, uint8_t CDC_Host_SendBreak(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
const uint8_t Duration) ATTR_NON_NULL_PTR_ARG(1); const uint8_t Duration) ATTR_NON_NULL_PTR_ARG(1);
/** Sends a given string to the attached USB device, if connected. If a device is not connected when the function is called, the /** Sends a given data buffer to the attached USB device, if connected. If a device is not connected when the function is
* string is discarded. Bytes will be queued for transmission to the device until either the pipe bank becomes full, or the * called, the data will be discarded. Bytes will be queued for transmission to the device until either the pipe bank
* \ref CDC_Host_Flush() function is called to flush the pending data to the host. This allows for multiple bytes to be * becomes full, or the \ref CDC_Host_Flush() function is called to flush the pending data to the device. This allows for
* packed into a single pipe packet, increasing data throughput. * multiple bytes to be packed into a single pipe packet, increasing data throughput.
*
* \pre This function must only be called when the Host state machine is in the \ref HOST_STATE_Configured state or the
* call will fail.
*
* \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class host configuration and state.
* \param[in] Buffer Pointer to a buffer containing the data to send to the device.
* \param[in] Length Length of the data to send to the device.
*
* \return A value from the \ref Pipe_Stream_RW_ErrorCodes_t enum.
*/
uint8_t CDC_Host_SendData(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
const uint8_t* const Buffer,
const uint16_t Length);
/** Sends a given null-terminated string to the attached USB device, if connected. If a device is not connected when the
* function is called, the string is discarded. Bytes will be queued for transmission to the device until either the pipe
* bank becomes full, or the \ref CDC_Host_Flush() function is called to flush the pending data to the device. This allows
* for multiple bytes to be packed into a single pipe packet, increasing data throughput.
* *
* \pre This function must only be called when the Host state machine is in the \ref HOST_STATE_Configured state or the * \pre This function must only be called when the Host state machine is in the \ref HOST_STATE_Configured state or the
* call will fail. * call will fail.
* *
* \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class host configuration and state. * \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class host configuration and state.
* \param[in] Data Pointer to the string to send to the device. * \param[in] String Pointer to the null terminated string to send to the device.
* \param[in] Length Size in bytes of the string to send to the device.
* *
* \return A value from the \ref Pipe_Stream_RW_ErrorCodes_t enum. * \return A value from the \ref Pipe_Stream_RW_ErrorCodes_t enum.
*/ */
uint8_t CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo, uint8_t CDC_Host_SendString(USB_ClassInfo_CDC_Host_t* const CDCInterfaceInfo,
const char* const Data, const char* const String) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2);
const uint16_t Length) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2);
/** Sends a given byte to the attached USB device, if connected. If a device is not connected when the function is called, the /** Sends a given byte to the attached USB device, if connected. If a device is not connected when the function is called, the
* byte is discarded. Bytes will be queued for transmission to the device until either the pipe bank becomes full, or the * byte is discarded. Bytes will be queued for transmission to the device until either the pipe bank becomes full, or the
......
...@@ -287,6 +287,29 @@ uint8_t PRNT_Host_SendByte(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo, ...@@ -287,6 +287,29 @@ uint8_t PRNT_Host_SendByte(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo,
} }
uint8_t PRNT_Host_SendString(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo, uint8_t PRNT_Host_SendString(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo,
void* String)
{
uint8_t ErrorCode;
if ((USB_HostState != HOST_STATE_Configured) || !(PRNTInterfaceInfo->State.IsActive))
return PIPE_RWSTREAM_DeviceDisconnected;
Pipe_SelectPipe(PRNTInterfaceInfo->Config.DataOUTPipeNumber);
Pipe_Unfreeze();
if ((ErrorCode = Pipe_Write_Stream_LE(String, strlen(String), NULL)) != PIPE_RWSTREAM_NoError)
return ErrorCode;
Pipe_ClearOUT();
ErrorCode = Pipe_WaitUntilReady();
Pipe_Freeze();
return ErrorCode;
}
uint8_t PRNT_Host_SendData(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo,
void* Buffer, void* Buffer,
const uint16_t Length) const uint16_t Length)
{ {
......
...@@ -182,6 +182,19 @@ ...@@ -182,6 +182,19 @@
*/ */
uint8_t PRNT_Host_Flush(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo) ATTR_NON_NULL_PTR_ARG(1); uint8_t PRNT_Host_Flush(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo) ATTR_NON_NULL_PTR_ARG(1);
/** Sends the given null terminated string to the attached printer's input endpoint.
*
* \pre This function must only be called when the Host state machine is in the \ref HOST_STATE_Configured state or the
* call will fail.
*
* \param[in,out] PRNTInterfaceInfo Pointer to a structure containing a Printer Class host configuration and state.
* \param[in] String Pointer to a null terminated string to send.
*
* \return A value from the \ref Pipe_Stream_RW_ErrorCodes_t enum.
*/
uint8_t PRNT_Host_SendString(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo,
void* String) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2);
/** Sends the given raw data stream to the attached printer's input endpoint. This should contain commands that the /** Sends the given raw data stream to the attached printer's input endpoint. This should contain commands that the
* printer is able to understand - for example, PCL data. Not all printers accept all printer languages; see * printer is able to understand - for example, PCL data. Not all printers accept all printer languages; see
* \ref PRNT_Host_GetDeviceID() for details on determining acceptable languages for an attached printer. * \ref PRNT_Host_GetDeviceID() for details on determining acceptable languages for an attached printer.
...@@ -195,7 +208,7 @@ ...@@ -195,7 +208,7 @@
* *
* \return A value from the \ref Pipe_Stream_RW_ErrorCodes_t enum. * \return A value from the \ref Pipe_Stream_RW_ErrorCodes_t enum.
*/ */
uint8_t PRNT_Host_SendString(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo, uint8_t PRNT_Host_SendData(USB_ClassInfo_PRNT_Host_t* const PRNTInterfaceInfo,
void* Buffer, void* Buffer,
const uint16_t Length) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2); const uint16_t Length) ATTR_NON_NULL_PTR_ARG(1) ATTR_NON_NULL_PTR_ARG(2);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
* - Added new HID_DESCRIPTOR_MOUSE, HID_DESCRIPTOR_KEYBOARD, HID_DESCRIPTOR_JOYSTICK and HID_DESCRIPTOR_VENDOR macros * - Added new HID_DESCRIPTOR_MOUSE, HID_DESCRIPTOR_KEYBOARD, HID_DESCRIPTOR_JOYSTICK and HID_DESCRIPTOR_VENDOR macros
* for easy automatic creation of basic USB HID device reports * for easy automatic creation of basic USB HID device reports
* - Added new MAX() and MIN() convenience macros * - Added new MAX() and MIN() convenience macros
* - Added new Serial_SendData() function to the Serial driver
* - Library Applications: * - Library Applications:
* - Added ability to write protect Mass Storage disk write operations from the host OS * - Added ability to write protect Mass Storage disk write operations from the host OS
* - Added new MIDIToneGenerator project * - Added new MIDIToneGenerator project
...@@ -45,6 +46,8 @@ ...@@ -45,6 +46,8 @@
* - Renamed the low level Serial byte send/receive functions, to be consistent with the CDC class driver byte functions * - Renamed the low level Serial byte send/receive functions, to be consistent with the CDC class driver byte functions
* - Altered the behaviour of the serial byte reception function so that is is non-blocking, and now returns a negative * - Altered the behaviour of the serial byte reception function so that is is non-blocking, and now returns a negative
* value if no character is received (to remain consistent with the CDC class driver byte reception routines) * value if no character is received (to remain consistent with the CDC class driver byte reception routines)
* - Renamed the PRNT_Host_SendString(), CDC_Host_SendString() and CDC_Device_SendString() functions to *_SendData(), and
* added new versions of the *_SendString() routines that expect a null terminated string instead
* - Library Applications: * - Library Applications:
* - Changed the XPLAINBridge software UART to use the regular timer CTC mode instead of the alternative CTC mode * - Changed the XPLAINBridge software UART to use the regular timer CTC mode instead of the alternative CTC mode
* via the Input Capture register, to reduce user confusion * via the Input Capture register, to reduce user confusion
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
* processed in the current transaction can be stored. If the \c BytesProcessed parameter is non \c NULL, each time the endpoint * processed in the current transaction can be stored. If the \c BytesProcessed parameter is non \c NULL, each time the endpoint
* bank becomes full and the packet is sent, the routine will exit with the new \ref ENDPOINT_RWSTREAM_IncompleteTransfer * bank becomes full and the packet is sent, the routine will exit with the new \ref ENDPOINT_RWSTREAM_IncompleteTransfer
* error code to allow the user application to determine when to send the next chunk of data. * error code to allow the user application to determine when to send the next chunk of data.
* - The \ref CDC_Device_SendString() function now expects a null terminated string instead of an explicit length. Existing code
* should use the new \ref CDC_Device_SendData() function, or remove the length parameter from the function call.
* *
* <b>Host Mode</b> * <b>Host Mode</b>
* - The Pipe stream functions now all require a \c BytesProcessed parameter instead of the previous callback parameter. * - The Pipe stream functions now all require a \c BytesProcessed parameter instead of the previous callback parameter.
...@@ -42,6 +44,9 @@ ...@@ -42,6 +44,9 @@
* processed in the current transaction can be stored. If the BytesProcessed parameter is non \c NULL, each time the pipe * processed in the current transaction can be stored. If the BytesProcessed parameter is non \c NULL, each time the pipe
* bank becomes full and the packet is sent, the routine will exit with the new \ref PIPE_RWSTREAM_IncompleteTransfer * bank becomes full and the packet is sent, the routine will exit with the new \ref PIPE_RWSTREAM_IncompleteTransfer
* error code to allow the user application to determine when to send the next chunk of data. * error code to allow the user application to determine when to send the next chunk of data.
* - The \ref PRNT_Host_SendString() and \ref CDC_Host_SendString() functions now expect a null terminated string instead of an explicit
* length. Existing code should use the new \ref PRNT_Host_SendData() and \ref CDC_Host_SendData() functions, or remove the
* length parameter from the function call.
* *
* \section Sec_Migration101122 Migrating from 100807 to 101122 * \section Sec_Migration101122 Migrating from 100807 to 101122
* <b>USB Core</b> * <b>USB Core</b>
......
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