forked from PAWPAW-Mirror/lib_xua
Merge pull request #126 from lucianomartin/feature/usb_str_cmds
Add new functions to read and write product and vendor strings
This commit is contained in:
@@ -20,7 +20,7 @@ lib_xua Change Log
|
|||||||
* CHANGE HID report descriptor to use generic events instead of GPI
|
* CHANGE HID report descriptor to use generic events instead of GPI
|
||||||
events, to report Key-phrase detection as AC Search, and to report end-call
|
events, to report Key-phrase detection as AC Search, and to report end-call
|
||||||
detection as AC Stop
|
detection as AC Stop
|
||||||
* ADDED: Ability to read or modify vendor and product IDs
|
* ADDED: Ability to read or modify vendor and product IDs and strings
|
||||||
* ADDED: Ability to read or modify bcdDevice
|
* ADDED: Ability to read or modify bcdDevice
|
||||||
* ADDED: Override USB descriptor with sampling frequency and
|
* ADDED: Override USB descriptor with sampling frequency and
|
||||||
bit-resolution set at boot time.
|
bit-resolution set at boot time.
|
||||||
|
|||||||
@@ -40,21 +40,73 @@ void XUA_Endpoint0_setVendorId(unsigned short vid);
|
|||||||
*/
|
*/
|
||||||
void XUA_Endpoint0_setProductId(unsigned short pid);
|
void XUA_Endpoint0_setProductId(unsigned short pid);
|
||||||
|
|
||||||
/** Function to get the Vendor ID value
|
|
||||||
*
|
|
||||||
* \return Vendor ID value
|
|
||||||
*/
|
|
||||||
unsigned short XUA_Endpoint0_getVendorId();
|
|
||||||
|
|
||||||
/** Function to get the Product ID value
|
/** Function to set the Vendor string
|
||||||
*
|
*
|
||||||
* \return Product ID value
|
* \param vendor_str Vendor string to set
|
||||||
*/
|
*/
|
||||||
unsigned short XUA_Endpoint0_getProductId();
|
#ifdef __XC__
|
||||||
|
void XUA_Endpoint0_setVendorStr(char * unsafe vendor_str);
|
||||||
|
#else
|
||||||
|
void XUA_Endpoint0_setVendorStr(char * vendor_str);
|
||||||
|
#endif
|
||||||
|
|
||||||
unsigned short XUA_Endpoint0_getBcdDevice();
|
/** Function to set the Product string
|
||||||
|
*
|
||||||
|
* \param product_str Product string to set
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __XC__
|
||||||
|
void XUA_Endpoint0_setProductStr(char * unsafe product_str);
|
||||||
|
#else
|
||||||
|
void XUA_Endpoint0_setProductStr(char * product_str);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Function to set the BCD device
|
||||||
|
*
|
||||||
|
* \param bcdDevice BCD device to set
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
void XUA_Endpoint0_setBcdDevice(unsigned short bcdDevice);
|
void XUA_Endpoint0_setBcdDevice(unsigned short bcdDevice);
|
||||||
|
|
||||||
|
/** Function to get the Vendor string
|
||||||
|
*
|
||||||
|
* \param vid vendor string
|
||||||
|
*/
|
||||||
|
#ifdef __XC__
|
||||||
|
char * unsafe XUA_Endpoint0_getVendorStr();
|
||||||
|
#else
|
||||||
|
char * XUA_Endpoint0_getVendorStr(;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Function to get the Product string
|
||||||
|
*
|
||||||
|
* \param pid Product string
|
||||||
|
*/
|
||||||
|
#ifdef __XC__
|
||||||
|
char * unsafe XUA_Endpoint0_getProductStr();
|
||||||
|
#else
|
||||||
|
char * XUA_Endpoint0_getProductStr(;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Function to get the Vendor string
|
||||||
|
*
|
||||||
|
* \return Vendor string
|
||||||
|
*/
|
||||||
|
unsigned short XUA_Endpoint0_getVendorId();
|
||||||
|
|
||||||
|
/** Function to get the Product string
|
||||||
|
*
|
||||||
|
* \return Product string
|
||||||
|
*/
|
||||||
|
unsigned short XUA_Endpoint0_getProductId();
|
||||||
|
|
||||||
|
/** Function to get the BCD device
|
||||||
|
*
|
||||||
|
* \return BCD device
|
||||||
|
*/
|
||||||
|
unsigned short XUA_Endpoint0_getBcdDevice();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <safestring.h>
|
#include <safestring.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
#include "xua.h"
|
#include "xua.h"
|
||||||
|
|
||||||
#if XUA_USB_EN
|
#if XUA_USB_EN
|
||||||
@@ -28,6 +28,11 @@
|
|||||||
#if DSD_CHANS_DAC > 0
|
#if DSD_CHANS_DAC > 0
|
||||||
#include "dsd_support.h"
|
#include "dsd_support.h"
|
||||||
#endif
|
#endif
|
||||||
|
#define DEBUG_UNIT XUA_EP0
|
||||||
|
#ifndef DEBUG_PRINT_ENABLE_XUA_EP0
|
||||||
|
#define DEBUG_PRINT_ENABLE_XUA_EP0 0
|
||||||
|
#endif // DEBUG_PRINT_ENABLE_XUA_EP0
|
||||||
|
#include "debug_print.h"
|
||||||
|
|
||||||
#include "xua_usb_params_funcs.h"
|
#include "xua_usb_params_funcs.h"
|
||||||
|
|
||||||
@@ -66,6 +71,9 @@ extern void device_reboot(void);
|
|||||||
#if( 0 < HID_CONTROLS )
|
#if( 0 < HID_CONTROLS )
|
||||||
#include "xua_hid.h"
|
#include "xua_hid.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef MIN
|
||||||
|
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||||
|
#endif
|
||||||
|
|
||||||
unsigned int DFU_mode_active = 0; // 0 - App active, 1 - DFU active
|
unsigned int DFU_mode_active = 0; // 0 - App active, 1 - DFU active
|
||||||
|
|
||||||
@@ -102,6 +110,13 @@ unsigned g_curStreamAlt_In = 0;
|
|||||||
/* Global variable for current USB bus speed (i.e. FS/HS) */
|
/* Global variable for current USB bus speed (i.e. FS/HS) */
|
||||||
XUD_BusSpeed_t g_curUsbSpeed = 0;
|
XUD_BusSpeed_t g_curUsbSpeed = 0;
|
||||||
|
|
||||||
|
/* Global variables for current USB Vendor and Product strings */
|
||||||
|
char g_vendor_str[XUA_MAX_STR_LEN] = VENDOR_STR;
|
||||||
|
#if (AUDIO_CLASS == 2)
|
||||||
|
char g_product_str[XUA_MAX_STR_LEN] = PRODUCT_STR_A2;
|
||||||
|
#else
|
||||||
|
char g_product_str[XUA_MAX_STR_LEN] = PRODUCT_STR_A1;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Subslot */
|
/* Subslot */
|
||||||
const unsigned g_subSlot_Out_HS[OUTPUT_FORMAT_COUNT] = {HS_STREAM_FORMAT_OUTPUT_1_SUBSLOT_BYTES,
|
const unsigned g_subSlot_Out_HS[OUTPUT_FORMAT_COUNT] = {HS_STREAM_FORMAT_OUTPUT_1_SUBSLOT_BYTES,
|
||||||
@@ -218,6 +233,82 @@ void XUA_Endpoint0_setVendorId(unsigned short vid) {
|
|||||||
#endif // AUDIO_CLASS == 1}
|
#endif // AUDIO_CLASS == 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void concatenateAndCopyStrings(char* string1, char* string2, char* string_buffer) {
|
||||||
|
memset(string_buffer, '\0', strlen(string_buffer));
|
||||||
|
|
||||||
|
uint32_t remaining_buffer_size = MIN(strlen(string1), XUA_MAX_STR_LEN-1);
|
||||||
|
strncpy(string_buffer, string1, remaining_buffer_size);
|
||||||
|
uint32_t total_string_size = MIN(strlen(string1)+strlen(string2), XUA_MAX_STR_LEN-1);
|
||||||
|
if (total_string_size==XUA_MAX_STR_LEN-1) {
|
||||||
|
remaining_buffer_size = XUA_MAX_STR_LEN-1-strlen(string1);
|
||||||
|
} else {
|
||||||
|
remaining_buffer_size = strlen(string1);
|
||||||
|
}
|
||||||
|
strncat(string_buffer, string2, remaining_buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XUA_Endpoint0_setStrTable() {
|
||||||
|
|
||||||
|
// update Vendor strings
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, "", g_strTable.vendorStr);
|
||||||
|
#if (AUDIO_CLASS == 2)
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " Clock Selector", g_strTable.clockSelectorStr);
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " Internal Clock", g_strTable.internalClockSourceStr);
|
||||||
|
#endif
|
||||||
|
#if SPDIF_RX
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " S/PDIF Clock", g_strTable.spdifClockSourceStr);
|
||||||
|
#endif
|
||||||
|
#if ADAT_RX
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " ADAT Clock", g_strTable.adatClockSourceStr);
|
||||||
|
#endif
|
||||||
|
#if (XUA_DFU_EN == 1)
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " DFU", g_strTable.dfuStr);
|
||||||
|
#endif
|
||||||
|
#ifdef USB_CONTROL_DESCS
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " Control", g_strTable.ctrlStr);
|
||||||
|
#endif
|
||||||
|
#ifdef MIDI
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " MIDI Out", g_strTable.midiOutStr);
|
||||||
|
concatenateAndCopyStrings(g_vendor_str, " MIDI In", g_strTable.midiInStr);
|
||||||
|
#endif
|
||||||
|
// update product strings
|
||||||
|
#if (AUDIO_CLASS_FALLBACK) || (AUDIO_CLASS == 1)
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.productStr_Audio1);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.outputInterfaceStr_Audio1);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.inputInterfaceStr_Audio1);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.usbInputTermStr_Audio1);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.usbOutputTermStr_Audio1);
|
||||||
|
#elif (AUDIO_CLASS == 2)
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.productStr_Audio2);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.outputInterfaceStr_Audio2);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.inputInterfaceStr_Audio2);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.usbInputTermStr_Audio2);
|
||||||
|
concatenateAndCopyStrings(g_product_str, "", g_strTable.usbOutputTermStr_Audio2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void XUA_Endpoint0_setVendorStr(char* vendor_str) {
|
||||||
|
debug_printf("XUA_Endpoint0_setVendorStr() with string %s", vendor_str);
|
||||||
|
concatenateAndCopyStrings(vendor_str, "", g_vendor_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XUA_Endpoint0_setProductStr(char* product_str) {
|
||||||
|
debug_printf("XUA_Endpoint0_setProductStr() with string %s", product_str);
|
||||||
|
concatenateAndCopyStrings(product_str, "", g_product_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* XUA_Endpoint0_getVendorStr() {
|
||||||
|
return g_strTable.vendorStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* XUA_Endpoint0_getProductStr() {
|
||||||
|
#if (AUDIO_CLASS_FALLBACK) || (AUDIO_CLASS == 1)
|
||||||
|
return g_strTable.productStr_Audio1;
|
||||||
|
#elif (AUDIO_CLASS == 2)
|
||||||
|
return g_strTable.productStr_Audio2;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void XUA_Endpoint0_setProductId(unsigned short pid) {
|
void XUA_Endpoint0_setProductId(unsigned short pid) {
|
||||||
#if (AUDIO_CLASS == 1)
|
#if (AUDIO_CLASS == 1)
|
||||||
devDesc_Audio1.idProduct = pid;
|
devDesc_Audio1.idProduct = pid;
|
||||||
@@ -270,6 +361,8 @@ void XUA_Endpoint0_init(chanend c_ep0_out, chanend c_ep0_in, chanend c_audioCont
|
|||||||
ep0_out = XUD_InitEp(c_ep0_out);
|
ep0_out = XUD_InitEp(c_ep0_out);
|
||||||
ep0_in = XUD_InitEp(c_ep0_in);
|
ep0_in = XUD_InitEp(c_ep0_in);
|
||||||
|
|
||||||
|
XUA_Endpoint0_setStrTable();
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
/* Dont need to init globals.. */
|
/* Dont need to init globals.. */
|
||||||
/* Init tables for volumes (+ 1 for master) */
|
/* Init tables for volumes (+ 1 for master) */
|
||||||
|
|||||||
@@ -26,7 +26,23 @@
|
|||||||
|
|
||||||
#define APPEND_PRODUCT_STR_A1(x) PRODUCT_STR_A1 " "#x
|
#define APPEND_PRODUCT_STR_A1(x) PRODUCT_STR_A1 " "#x
|
||||||
|
|
||||||
#define STR_TABLE_ENTRY(name) char *name
|
#define STR_TABLE_ENTRY(name) char * name
|
||||||
|
|
||||||
|
// The empty strings below are used in the g_strTable to set the maximum size of the table entries
|
||||||
|
// The last char of the strings are different, so that the compiler allocates separate memory spaces
|
||||||
|
#define XUA_VENDOR_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01"
|
||||||
|
#define XUA_PRODUCT_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\02"
|
||||||
|
#define XUA_CLOCK_SELECTOR_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\03"
|
||||||
|
#define XUA_INTERNAL_CLOCK_SELECTOR_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\04"
|
||||||
|
#define XUA_SPDIF_CLOCK_SOURCE_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\05"
|
||||||
|
#define XUA_ADAT_CLOCK_SOURCE_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\06"
|
||||||
|
#define XUA_DFU_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\07"
|
||||||
|
#define XUA_CTRL_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\08"
|
||||||
|
#define XUA_MIDI_OUT_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\09"
|
||||||
|
#define XUA_MIDI_IN_EMPTY_STRING "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0a"
|
||||||
|
|
||||||
|
// The value below must match the length of XUA_DESCR_EMPTY_STRING.
|
||||||
|
#define XUA_MAX_STR_LEN (32)
|
||||||
|
|
||||||
#define ISO_EP_ATTRIBUTES_ASYNCH 0x05 //ISO, ASYNCH, DATA EP
|
#define ISO_EP_ATTRIBUTES_ASYNCH 0x05 //ISO, ASYNCH, DATA EP
|
||||||
#define ISO_EP_ATTRIBUTES_ADAPTIVE 0x09 //ISO, ADAPTIVE, DATA EP
|
#define ISO_EP_ATTRIBUTES_ADAPTIVE 0x09 //ISO, ADAPTIVE, DATA EP
|
||||||
@@ -317,41 +333,42 @@ typedef struct
|
|||||||
StringDescTable_t g_strTable =
|
StringDescTable_t g_strTable =
|
||||||
{
|
{
|
||||||
.langID = "\x09\x04", /* US English */
|
.langID = "\x09\x04", /* US English */
|
||||||
.vendorStr = VENDOR_STR,
|
.vendorStr = XUA_VENDOR_EMPTY_STRING,
|
||||||
.serialStr = "",
|
.serialStr = "",
|
||||||
#if (AUDIO_CLASS == 2)
|
#if (AUDIO_CLASS == 2)
|
||||||
.productStr_Audio2 = PRODUCT_STR_A2,
|
.productStr_Audio2 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.outputInterfaceStr_Audio2 = APPEND_PRODUCT_STR_A2(),
|
.outputInterfaceStr_Audio2 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.inputInterfaceStr_Audio2 = APPEND_PRODUCT_STR_A2(),
|
.inputInterfaceStr_Audio2 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.usbInputTermStr_Audio2 = APPEND_PRODUCT_STR_A2(),
|
.usbInputTermStr_Audio2 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.usbOutputTermStr_Audio2 = APPEND_PRODUCT_STR_A2(),
|
.usbOutputTermStr_Audio2 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
#if (AUDIO_CLASS_FALLBACK) || (AUDIO_CLASS == 1)
|
#if (AUDIO_CLASS_FALLBACK) || (AUDIO_CLASS == 1)
|
||||||
.productStr_Audio1 = PRODUCT_STR_A1,
|
|
||||||
.outputInterfaceStr_Audio1 = APPEND_PRODUCT_STR_A1(),
|
.productStr_Audio1 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.inputInterfaceStr_Audio1 = APPEND_PRODUCT_STR_A1(),
|
.outputInterfaceStr_Audio1 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.usbInputTermStr_Audio1 = APPEND_PRODUCT_STR_A1(),
|
.inputInterfaceStr_Audio1 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
.usbOutputTermStr_Audio1 = APPEND_PRODUCT_STR_A1(),
|
.usbInputTermStr_Audio1 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
|
.usbOutputTermStr_Audio1 = XUA_PRODUCT_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
#if (AUDIO_CLASS == 2)
|
#if (AUDIO_CLASS == 2)
|
||||||
.clockSelectorStr = APPEND_VENDOR_STR(Clock Selector),
|
.clockSelectorStr = XUA_CLOCK_SELECTOR_EMPTY_STRING,
|
||||||
.internalClockSourceStr = APPEND_VENDOR_STR(Internal Clock),
|
.internalClockSourceStr = XUA_INTERNAL_CLOCK_SELECTOR_EMPTY_STRING,
|
||||||
#if SPDIF_RX
|
#if SPDIF_RX
|
||||||
.spdifClockSourceStr = APPEND_VENDOR_STR(S/PDIF Clock),
|
.spdifClockSourceStr = XUA_SPDIF_CLOCK_SOURCE_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
#if ADAT_RX
|
#if ADAT_RX
|
||||||
.adatClockSourceStr = APPEND_VENDOR_STR(ADAT Clock),
|
.adatClockSourceStr = XUA_ADAT_CLOCK_SOURCE_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#if (XUA_DFU_EN == 1)
|
#if (XUA_DFU_EN == 1)
|
||||||
.dfuStr = APPEND_VENDOR_STR(DFU),
|
.dfuStr = XUA_DFU_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
#ifdef USB_CONTROL_DESCS
|
#ifdef USB_CONTROL_DESCS
|
||||||
.ctrlStr = APPEND_VENDOR_STR(Control),
|
.ctrlStr = XUA_CTRL_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
#ifdef MIDI
|
#ifdef MIDI
|
||||||
.midiOutStr = APPEND_VENDOR_STR(MIDI Out),
|
.midiOutStr = XUA_MIDI_OUT_EMPTY_STRING,
|
||||||
.midiInStr = APPEND_VENDOR_STR(MIDI In),
|
.midiInStr = XUA_MIDI_IN_EMPTY_STRING,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "chanstrings.h"
|
#include "chanstrings.h"
|
||||||
@@ -2293,6 +2310,8 @@ unsigned char cfgDesc_Null[] =
|
|||||||
* To work around this we repeat MAX_FREQ_FS multiple times in some cases */
|
* To work around this we repeat MAX_FREQ_FS multiple times in some cases */
|
||||||
|
|
||||||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
#define MAX(a,b) (((a)>(b))?(a):(b))
|
||||||
|
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||||
|
|
||||||
const unsigned num_freqs_a1 = MAX(3, (0
|
const unsigned num_freqs_a1 = MAX(3, (0
|
||||||
#if(MIN_FREQ <= 8000) && (MAX_FREQ_FS >= 8000)
|
#if(MIN_FREQ <= 8000) && (MAX_FREQ_FS >= 8000)
|
||||||
+ 1
|
+ 1
|
||||||
|
|||||||
Reference in New Issue
Block a user