staging: ozwpan: ISOC transfer in triggered mode
This patch implements ISOC frame transfer while PD is in triggered mode. Signed-off-by: Rupesh Gujare <rgujare@ozmodevices.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Родитель
3403cc0f70
Коммит
33e6ada17f
|
@ -59,6 +59,6 @@ module_exit(ozwpan_exit);
|
||||||
|
|
||||||
MODULE_AUTHOR("Chris Kelly");
|
MODULE_AUTHOR("Chris Kelly");
|
||||||
MODULE_DESCRIPTION("Ozmo Devices USB over WiFi hcd driver");
|
MODULE_DESCRIPTION("Ozmo Devices USB over WiFi hcd driver");
|
||||||
MODULE_VERSION("1.0.9");
|
MODULE_VERSION("1.0.10");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
||||||
|
|
|
@ -24,18 +24,22 @@
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
#define OZ_MAX_TX_POOL_SIZE 6
|
#define OZ_MAX_TX_POOL_SIZE 6
|
||||||
/* Maximum number of uncompleted isoc frames that can be pending.
|
/* Maximum number of uncompleted isoc frames that can be pending in network.
|
||||||
*/
|
*/
|
||||||
#define OZ_MAX_SUBMITTED_ISOC 16
|
#define OZ_MAX_SUBMITTED_ISOC 16
|
||||||
|
/* Maximum number of uncompleted isoc frames that can be pending in Tx Queue.
|
||||||
|
*/
|
||||||
|
#define OZ_MAX_TX_QUEUE_ISOC 32
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
static struct oz_tx_frame *oz_tx_frame_alloc(struct oz_pd *pd);
|
static struct oz_tx_frame *oz_tx_frame_alloc(struct oz_pd *pd);
|
||||||
static void oz_tx_frame_free(struct oz_pd *pd, struct oz_tx_frame *f);
|
static void oz_tx_frame_free(struct oz_pd *pd, struct oz_tx_frame *f);
|
||||||
|
static void oz_tx_isoc_free(struct oz_pd *pd, struct oz_tx_frame *f);
|
||||||
static struct sk_buff *oz_build_frame(struct oz_pd *pd, struct oz_tx_frame *f);
|
static struct sk_buff *oz_build_frame(struct oz_pd *pd, struct oz_tx_frame *f);
|
||||||
static int oz_send_isoc_frame(struct oz_pd *pd);
|
static int oz_send_isoc_frame(struct oz_pd *pd);
|
||||||
static void oz_retire_frame(struct oz_pd *pd, struct oz_tx_frame *f);
|
static void oz_retire_frame(struct oz_pd *pd, struct oz_tx_frame *f);
|
||||||
static void oz_isoc_stream_free(struct oz_isoc_stream *st);
|
static void oz_isoc_stream_free(struct oz_isoc_stream *st);
|
||||||
static int oz_send_next_queued_frame(struct oz_pd *pd, int *more_data);
|
static int oz_send_next_queued_frame(struct oz_pd *pd, int more_data);
|
||||||
static void oz_isoc_destructor(struct sk_buff *skb);
|
static void oz_isoc_destructor(struct sk_buff *skb);
|
||||||
static int oz_def_app_init(void);
|
static int oz_def_app_init(void);
|
||||||
static void oz_def_app_term(void);
|
static void oz_def_app_term(void);
|
||||||
|
@ -208,6 +212,8 @@ void oz_pd_destroy(struct oz_pd *pd)
|
||||||
while (e != &pd->tx_queue) {
|
while (e != &pd->tx_queue) {
|
||||||
f = container_of(e, struct oz_tx_frame, link);
|
f = container_of(e, struct oz_tx_frame, link);
|
||||||
e = e->next;
|
e = e->next;
|
||||||
|
if (f->skb != NULL)
|
||||||
|
kfree_skb(f->skb);
|
||||||
oz_retire_frame(pd, f);
|
oz_retire_frame(pd, f);
|
||||||
}
|
}
|
||||||
oz_elt_buf_term(&pd->elt_buff);
|
oz_elt_buf_term(&pd->elt_buff);
|
||||||
|
@ -372,6 +378,23 @@ static struct oz_tx_frame *oz_tx_frame_alloc(struct oz_pd *pd)
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
* Context: softirq or process
|
||||||
|
*/
|
||||||
|
static void oz_tx_isoc_free(struct oz_pd *pd, struct oz_tx_frame *f)
|
||||||
|
{
|
||||||
|
pd->nb_queued_isoc_frames--;
|
||||||
|
list_del_init(&f->link);
|
||||||
|
if (pd->tx_pool_count < OZ_MAX_TX_POOL_SIZE) {
|
||||||
|
f->link.next = pd->tx_pool;
|
||||||
|
pd->tx_pool = &f->link;
|
||||||
|
pd->tx_pool_count++;
|
||||||
|
} else {
|
||||||
|
kfree(f);
|
||||||
|
}
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES, "Releasing ISOC Frame isoc_nb= %d\n",
|
||||||
|
pd->nb_queued_isoc_frames);
|
||||||
|
}
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* Context: softirq or process
|
* Context: softirq or process
|
||||||
*/
|
*/
|
||||||
|
@ -388,6 +411,14 @@ static void oz_tx_frame_free(struct oz_pd *pd, struct oz_tx_frame *f)
|
||||||
if (f)
|
if (f)
|
||||||
kfree(f);
|
kfree(f);
|
||||||
}
|
}
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
* Context: softirq-serialized
|
||||||
|
*/
|
||||||
|
void oz_set_more_bit(struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct oz_hdr *oz_hdr = (struct oz_hdr *)skb_network_header(skb);
|
||||||
|
oz_hdr->control |= OZ_F_MORE_DATA;
|
||||||
|
}
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* Context: softirq
|
* Context: softirq
|
||||||
*/
|
*/
|
||||||
|
@ -403,6 +434,7 @@ int oz_prepare_frame(struct oz_pd *pd, int empty)
|
||||||
f = oz_tx_frame_alloc(pd);
|
f = oz_tx_frame_alloc(pd);
|
||||||
if (f == 0)
|
if (f == 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
f->skb = NULL;
|
||||||
f->hdr.control =
|
f->hdr.control =
|
||||||
(OZ_PROTOCOL_VERSION<<OZ_VERSION_SHIFT) | OZ_F_ACK_REQUESTED;
|
(OZ_PROTOCOL_VERSION<<OZ_VERSION_SHIFT) | OZ_F_ACK_REQUESTED;
|
||||||
++pd->last_tx_pkt_num;
|
++pd->last_tx_pkt_num;
|
||||||
|
@ -486,24 +518,51 @@ static void oz_retire_frame(struct oz_pd *pd, struct oz_tx_frame *f)
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* Context: softirq-serialized
|
* Context: softirq-serialized
|
||||||
*/
|
*/
|
||||||
static int oz_send_next_queued_frame(struct oz_pd *pd, int *more_data)
|
static int oz_send_next_queued_frame(struct oz_pd *pd, int more_data)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
struct oz_tx_frame *f;
|
struct oz_tx_frame *f;
|
||||||
struct list_head *e;
|
struct list_head *e;
|
||||||
*more_data = 0;
|
|
||||||
spin_lock(&pd->tx_frame_lock);
|
spin_lock(&pd->tx_frame_lock);
|
||||||
e = pd->last_sent_frame->next;
|
e = pd->last_sent_frame->next;
|
||||||
if (e == &pd->tx_queue) {
|
if (e == &pd->tx_queue) {
|
||||||
spin_unlock(&pd->tx_frame_lock);
|
spin_unlock(&pd->tx_frame_lock);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
pd->last_sent_frame = e;
|
|
||||||
if (e->next != &pd->tx_queue)
|
|
||||||
*more_data = 1;
|
|
||||||
f = container_of(e, struct oz_tx_frame, link);
|
f = container_of(e, struct oz_tx_frame, link);
|
||||||
|
|
||||||
|
if (f->skb != NULL) {
|
||||||
|
skb = f->skb;
|
||||||
|
oz_tx_isoc_free(pd, f);
|
||||||
|
spin_unlock(&pd->tx_frame_lock);
|
||||||
|
if (more_data)
|
||||||
|
oz_set_more_bit(skb);
|
||||||
|
if ((int)atomic_read(&g_submitted_isoc) <
|
||||||
|
OZ_MAX_SUBMITTED_ISOC) {
|
||||||
|
if (dev_queue_xmit(skb) < 0) {
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES,
|
||||||
|
"Dropping ISOC Frame\n");
|
||||||
|
oz_event_log(OZ_EVT_TX_ISOC_DROP, 0, 0, 0, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
atomic_inc(&g_submitted_isoc);
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES,
|
||||||
|
"Sending ISOC Frame, nb_isoc= %d\n",
|
||||||
|
pd->nb_queued_isoc_frames);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
kfree_skb(skb);
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES, "Dropping ISOC Frame>\n");
|
||||||
|
oz_event_log(OZ_EVT_TX_ISOC_DROP, 0, 0, 0, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pd->last_sent_frame = e;
|
||||||
skb = oz_build_frame(pd, f);
|
skb = oz_build_frame(pd, f);
|
||||||
spin_unlock(&pd->tx_frame_lock);
|
spin_unlock(&pd->tx_frame_lock);
|
||||||
|
if (more_data)
|
||||||
|
oz_set_more_bit(skb);
|
||||||
oz_trace2(OZ_TRACE_TX_FRAMES, "TX frame PN=0x%x\n", f->hdr.pkt_num);
|
oz_trace2(OZ_TRACE_TX_FRAMES, "TX frame PN=0x%x\n", f->hdr.pkt_num);
|
||||||
if (skb) {
|
if (skb) {
|
||||||
oz_event_log(OZ_EVT_TX_FRAME,
|
oz_event_log(OZ_EVT_TX_FRAME,
|
||||||
|
@ -512,6 +571,7 @@ static int oz_send_next_queued_frame(struct oz_pd *pd, int *more_data)
|
||||||
0, f->hdr.pkt_num);
|
0, f->hdr.pkt_num);
|
||||||
if (dev_queue_xmit(skb) < 0)
|
if (dev_queue_xmit(skb) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -520,21 +580,38 @@ static int oz_send_next_queued_frame(struct oz_pd *pd, int *more_data)
|
||||||
*/
|
*/
|
||||||
void oz_send_queued_frames(struct oz_pd *pd, int backlog)
|
void oz_send_queued_frames(struct oz_pd *pd, int backlog)
|
||||||
{
|
{
|
||||||
int more;
|
while (oz_prepare_frame(pd, 0) >= 0)
|
||||||
if (backlog < OZ_MAX_QUEUED_FRAMES) {
|
backlog++;
|
||||||
if (oz_send_next_queued_frame(pd, &more) >= 0) {
|
|
||||||
while (more && oz_send_next_queued_frame(pd, &more))
|
switch (pd->mode & (OZ_F_ISOC_NO_ELTS | OZ_F_ISOC_ANYTIME)) {
|
||||||
;
|
|
||||||
} else {
|
case OZ_F_ISOC_NO_ELTS: {
|
||||||
if (((pd->mode & OZ_F_ISOC_ANYTIME) == 0)
|
backlog += pd->nb_queued_isoc_frames;
|
||||||
|| (pd->isoc_sent == 0)) {
|
if (backlog <= 0)
|
||||||
if (oz_prepare_frame(pd, 1) >= 0)
|
goto out;
|
||||||
oz_send_next_queued_frame(pd, &more);
|
if (backlog > OZ_MAX_SUBMITTED_ISOC)
|
||||||
}
|
backlog = OZ_MAX_SUBMITTED_ISOC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OZ_NO_ELTS_ANYTIME: {
|
||||||
|
if ((backlog <= 0) && (pd->isoc_sent == 0))
|
||||||
|
goto out;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (backlog <= 0)
|
||||||
|
goto out;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
oz_send_next_queued_frame(pd, &more);
|
|
||||||
}
|
}
|
||||||
|
while (backlog--) {
|
||||||
|
if (oz_send_next_queued_frame(pd, backlog) < 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
out: oz_prepare_frame(pd, 1);
|
||||||
|
oz_send_next_queued_frame(pd, 0);
|
||||||
}
|
}
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* Context: softirq
|
* Context: softirq
|
||||||
|
@ -603,8 +680,10 @@ void oz_retire_tx_frames(struct oz_pd *pd, u8 lpn)
|
||||||
f = container_of(e, struct oz_tx_frame, link);
|
f = container_of(e, struct oz_tx_frame, link);
|
||||||
pkt_num = le32_to_cpu(get_unaligned(&f->hdr.pkt_num));
|
pkt_num = le32_to_cpu(get_unaligned(&f->hdr.pkt_num));
|
||||||
diff = (lpn - (pkt_num & OZ_LAST_PN_MASK)) & OZ_LAST_PN_MASK;
|
diff = (lpn - (pkt_num & OZ_LAST_PN_MASK)) & OZ_LAST_PN_MASK;
|
||||||
if (diff > OZ_LAST_PN_HALF_CYCLE)
|
if ((diff > OZ_LAST_PN_HALF_CYCLE) || (pkt_num == 0))
|
||||||
break;
|
break;
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES, "Releasing pkt_num= %u, nb= %d\n",
|
||||||
|
pkt_num, pd->nb_queued_frames);
|
||||||
if (first == 0)
|
if (first == 0)
|
||||||
first = e;
|
first = e;
|
||||||
last = e;
|
last = e;
|
||||||
|
@ -756,21 +835,53 @@ int oz_send_isoc_unit(struct oz_pd *pd, u8 ep_num, u8 *data, int len)
|
||||||
memcpy(oz_hdr, &oz, sizeof(oz));
|
memcpy(oz_hdr, &oz, sizeof(oz));
|
||||||
memcpy(oz_hdr+1, &iso, sizeof(iso));
|
memcpy(oz_hdr+1, &iso, sizeof(iso));
|
||||||
if (dev_hard_header(skb, dev, OZ_ETHERTYPE, pd->mac_addr,
|
if (dev_hard_header(skb, dev, OZ_ETHERTYPE, pd->mac_addr,
|
||||||
dev->dev_addr, skb->len) < 0) {
|
dev->dev_addr, skb->len) < 0)
|
||||||
kfree_skb(skb);
|
goto out;
|
||||||
return -1;
|
|
||||||
|
skb->destructor = oz_isoc_destructor;
|
||||||
|
/*Queue for Xmit if mode is not ANYTIME*/
|
||||||
|
if (!(pd->mode & OZ_F_ISOC_ANYTIME)) {
|
||||||
|
struct oz_tx_frame *isoc_unit = NULL;
|
||||||
|
int nb = pd->nb_queued_isoc_frames;
|
||||||
|
if (nb >= OZ_MAX_TX_QUEUE_ISOC) {
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES,
|
||||||
|
"Dropping ISOC Unit nb= %d\n",
|
||||||
|
nb);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
isoc_unit = oz_tx_frame_alloc(pd);
|
||||||
|
if (isoc_unit == NULL)
|
||||||
|
goto out;
|
||||||
|
isoc_unit->hdr = oz;
|
||||||
|
isoc_unit->skb = skb;
|
||||||
|
spin_lock_bh(&pd->tx_frame_lock);
|
||||||
|
list_add_tail(&isoc_unit->link, &pd->tx_queue);
|
||||||
|
pd->nb_queued_isoc_frames++;
|
||||||
|
spin_unlock_bh(&pd->tx_frame_lock);
|
||||||
|
oz_trace2(OZ_TRACE_TX_FRAMES,
|
||||||
|
"Added ISOC Frame to Tx Queue isoc_nb= %d, nb= %d\n",
|
||||||
|
pd->nb_queued_isoc_frames, pd->nb_queued_frames);
|
||||||
|
oz_event_log(OZ_EVT_TX_ISOC, nb_units, iso.frame_number,
|
||||||
|
skb, atomic_read(&g_submitted_isoc));
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*In ANYTIME mode Xmit unit immediately*/
|
||||||
if (atomic_read(&g_submitted_isoc) < OZ_MAX_SUBMITTED_ISOC) {
|
if (atomic_read(&g_submitted_isoc) < OZ_MAX_SUBMITTED_ISOC) {
|
||||||
skb->destructor = oz_isoc_destructor;
|
|
||||||
atomic_inc(&g_submitted_isoc);
|
atomic_inc(&g_submitted_isoc);
|
||||||
oz_event_log(OZ_EVT_TX_ISOC, nb_units, iso.frame_number,
|
oz_event_log(OZ_EVT_TX_ISOC, nb_units, iso.frame_number,
|
||||||
skb, atomic_read(&g_submitted_isoc));
|
skb, atomic_read(&g_submitted_isoc));
|
||||||
if (dev_queue_xmit(skb) < 0)
|
if (dev_queue_xmit(skb) < 0) {
|
||||||
|
oz_event_log(OZ_EVT_TX_ISOC_DROP, 0, 0, 0, 0);
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else
|
||||||
oz_event_log(OZ_EVT_TX_ISOC_DROP, 0, 0, 0, 0);
|
return 0;
|
||||||
kfree_skb(skb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out: oz_event_log(OZ_EVT_TX_ISOC_DROP, 0, 0, 0, 0);
|
||||||
|
kfree_skb(skb);
|
||||||
|
return -1;
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct oz_tx_frame {
|
||||||
struct list_head link;
|
struct list_head link;
|
||||||
struct list_head elt_list;
|
struct list_head elt_list;
|
||||||
struct oz_hdr hdr;
|
struct oz_hdr hdr;
|
||||||
|
struct sk_buff *skb;
|
||||||
int total_size;
|
int total_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ struct oz_pd {
|
||||||
u8 ms_per_isoc;
|
u8 ms_per_isoc;
|
||||||
unsigned max_stream_buffering;
|
unsigned max_stream_buffering;
|
||||||
int nb_queued_frames;
|
int nb_queued_frames;
|
||||||
|
int nb_queued_isoc_frames;
|
||||||
struct list_head *tx_pool;
|
struct list_head *tx_pool;
|
||||||
int tx_pool_count;
|
int tx_pool_count;
|
||||||
spinlock_t tx_frame_lock;
|
spinlock_t tx_frame_lock;
|
||||||
|
@ -118,4 +120,3 @@ void oz_apps_init(void);
|
||||||
void oz_apps_term(void);
|
void oz_apps_term(void);
|
||||||
|
|
||||||
#endif /* Sentry */
|
#endif /* Sentry */
|
||||||
|
|
||||||
|
|
|
@ -217,7 +217,6 @@ static struct oz_pd *oz_connect_req(struct oz_pd *cur_pd, struct oz_elt *elt,
|
||||||
pd->mode = body->mode;
|
pd->mode = body->mode;
|
||||||
pd->pd_info = body->pd_info;
|
pd->pd_info = body->pd_info;
|
||||||
if (pd->mode & OZ_F_ISOC_NO_ELTS) {
|
if (pd->mode & OZ_F_ISOC_NO_ELTS) {
|
||||||
pd->mode |= OZ_F_ISOC_ANYTIME;
|
|
||||||
pd->ms_per_isoc = body->ms_per_isoc;
|
pd->ms_per_isoc = body->ms_per_isoc;
|
||||||
if (!pd->ms_per_isoc)
|
if (!pd->ms_per_isoc)
|
||||||
pd->ms_per_isoc = 4;
|
pd->ms_per_isoc = 4;
|
||||||
|
@ -366,6 +365,7 @@ static void oz_rx_frame(struct sk_buff *skb)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pd && !dup && ((pd->mode & OZ_MODE_MASK) == OZ_MODE_TRIGGERED)) {
|
if (pd && !dup && ((pd->mode & OZ_MODE_MASK) == OZ_MODE_TRIGGERED)) {
|
||||||
|
oz_trace2(OZ_TRACE_RX_FRAMES, "Received TRIGGER Frame\n");
|
||||||
pd->last_sent_frame = &pd->tx_queue;
|
pd->last_sent_frame = &pd->tx_queue;
|
||||||
if (oz_hdr->control & OZ_F_ACK) {
|
if (oz_hdr->control & OZ_F_ACK) {
|
||||||
/* Retire completed frames */
|
/* Retire completed frames */
|
||||||
|
@ -376,8 +376,6 @@ static void oz_rx_frame(struct sk_buff *skb)
|
||||||
int backlog = pd->nb_queued_frames;
|
int backlog = pd->nb_queued_frames;
|
||||||
pd->trigger_pkt_num = pkt_num;
|
pd->trigger_pkt_num = pkt_num;
|
||||||
/* Send queued frames */
|
/* Send queued frames */
|
||||||
while (oz_prepare_frame(pd, 0) >= 0)
|
|
||||||
;
|
|
||||||
oz_send_queued_frames(pd, backlog);
|
oz_send_queued_frames(pd, backlog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ struct oz_elt_connect_req {
|
||||||
#define OZ_MODE_MASK 0xf
|
#define OZ_MODE_MASK 0xf
|
||||||
#define OZ_F_ISOC_NO_ELTS 0x40
|
#define OZ_F_ISOC_NO_ELTS 0x40
|
||||||
#define OZ_F_ISOC_ANYTIME 0x80
|
#define OZ_F_ISOC_ANYTIME 0x80
|
||||||
|
#define OZ_NO_ELTS_ANYTIME 0xc0
|
||||||
|
|
||||||
/* Keep alive field.
|
/* Keep alive field.
|
||||||
*/
|
*/
|
||||||
|
|
Загрузка…
Ссылка в новой задаче