Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
TASTE
PolyORB-HI-C
Commits
b4d60d63
Commit
b4d60d63
authored
Jan 14, 2015
by
yoogx
Browse files
* Add support for multi-core systems: assignment of thread
affinity for GNU/Linux and RTEMS platforms
parent
5ce7aff6
Changes
4
Hide whitespace changes
Inline
Side-by-side
include/po_hi_task.h
View file @
b4d60d63
...
...
@@ -5,14 +5,14 @@
*
* For more informations, please visit http://taste.tuxfamily.org/wiki
*
* Copyright (C) 2010-201
4
ESA & ISAE.
* Copyright (C) 2010-201
5
ESA & ISAE.
*/
#ifndef __PO_HI_TASK_H__
#define __PO_HI_TASK_H__
/*
* Define some values that are dependant of the
* Define some values that are dependant of the
* underlying executive.
*/
#if defined(POSIX) || defined (XENO_POSIX)
...
...
@@ -30,7 +30,7 @@
#include
<stdio.h>
#define __PO_HI_MAIN_NAME _tmain
#define __PO_HI_MAIN_TYPE int
#define __PO_HI_MAIN_ARGS
#define __PO_HI_MAIN_ARGS
#define __PO_HI_MAIN_RETURN EXIT_SUCCESS
#define __ERRORMSG(s, args...) fprintf(stderr, s, ##args)
#elif defined (XENO_NATIVE)
...
...
@@ -50,7 +50,7 @@
#elif defined(RTEMS_POSIX)
#define __PO_HI_MAIN_NAME POSIX_Init
#define __PO_HI_MAIN_TYPE int
#define __PO_HI_MAIN_ARGS
#define __PO_HI_MAIN_ARGS
#define __PO_HI_MAIN_RETURN 0
#define __ERRORMSG(s, args...) fprintf(stderr, s, ##args)
#endif
...
...
@@ -100,38 +100,40 @@ typedef size_t __po_hi_stack_t;
int
__po_hi_initialize_tasking
(
void
);
/*
* Create a periodic task.
*
* Create a periodic task.
*
* The task created have the identifier given by the first
* parameter. It is created according to the period created
* with __po_hi_* functions (like __po_hi_milliseconds())
* and priority parameters (use the OS priority). The task execute
* periodically start_routine.
*
* This function returns SUCCESS if there is no error. Else,
* This function returns SUCCESS if there is no error. Else,
* it returns the negative value ERROR_CREATE_TASK.
*/
int
__po_hi_create_periodic_task
(
const
__po_hi_task_id
id
,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
int
__po_hi_create_periodic_task
(
const
__po_hi_task_id
id
,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_stack_t
stack_size
,
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
));
/*
* Create a sporadic task.
* Create a sporadic task.
*
* The identifier of the task is the first parameter. The period and
* the priority of the task are stored in the second and third
* parameter. The code executed by the task is stored in the
* start_routine pointer.
*
*
* Returns SUCCESS value if there is no error. Else, returns the negative
* value ERROR_CREATE_TASK
*/
int
__po_hi_create_sporadic_task
(
const
__po_hi_task_id
id
,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_stack_t
stack_size
,
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
));
/*
...
...
@@ -141,14 +143,15 @@ int __po_hi_create_sporadic_task (const __po_hi_task_id id,
* the priority of the task are stored in the second and third
* parameter. The code executed by the task is stored in the
* start_routine pointer.
*
*
* Returns SUCCESS value if there is no error. Else, returns the negative
* value ERROR_CREATE_TASK
*/
int
__po_hi_create_generic_task
(
const
__po_hi_task_id
id
,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
int
__po_hi_create_generic_task
(
const
__po_hi_task_id
id
,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_stack_t
stack_size
,
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
),
void
*
arg
);
...
...
@@ -193,4 +196,4 @@ void __po_hi_tasks_killall (void);
void
__po_hi_task_wait_offset
(
const
__po_hi_time_t
*
time
);
#endif
/* __PO_HI_TASK_H__ */
#endif
/* __PO_HI_TASK_H__ */
src/drivers/po_hi_driver_leon_eth.c
View file @
b4d60d63
...
...
@@ -5,7 +5,7 @@
*
* For more informations, please visit http://taste.tuxfamily.org/wiki
*
* Copyright (C) 2011-201
4
ESA & ISAE.
* Copyright (C) 2011-201
5
ESA & ISAE.
*/
#include
<deployment.h>
...
...
@@ -97,7 +97,7 @@ static struct rtems_bsdnet_ifconfig netdriver_config = {
#ifdef RTEMS48
loopback_config,
#else
0,
0,
#endif
*/
"255.255.255.255"
,
/* IP address */
...
...
@@ -144,7 +144,7 @@ void __po_hi_c_driver_eth_leon_poller (const __po_hi_device_id dev_id)
struct
sockaddr_in
sa
;
__po_hi_device_id
dev
;
__po_hi_node_t
dev_init
;
int
established
=
0
;
int
established
=
0
;
__po_hi_protocol_conf_t
*
protocol_conf
;
unsigned
long
*
swap_pointer
;
...
...
@@ -207,7 +207,7 @@ void __po_hi_c_driver_eth_leon_poller (const __po_hi_device_id dev_id)
if
(
sock
>
max_socket
)
{
max_socket
=
sock
;
}
}
}
}
__DEBUGMSG
(
"[DRIVER ETH] Poller initialization finished, waiting for other tasks
\n
"
);
...
...
@@ -233,7 +233,7 @@ void __po_hi_c_driver_eth_leon_poller (const __po_hi_device_id dev_id)
{
#ifdef __PO_HI_DEBUG
__DEBUGMSG
(
"[DRIVER ETH] Error on select for node %d
\n
"
,
__po_hi_mynode
);
#endif
#endif
}
#ifdef __PO_HI_DEBUG
__DEBUGMSG
(
"[DRIVER ETH] Receive message
\n
"
);
...
...
@@ -293,7 +293,7 @@ void __po_hi_c_driver_eth_leon_poller (const __po_hi_device_id dev_id)
__po_hi_main_deliver
(
&
__po_hi_c_driver_eth_leon_poller_received_request
);
}
}
}
}
}
#endif
...
...
@@ -382,7 +382,7 @@ void __po_hi_c_driver_eth_leon_init (__po_hi_device_id id)
__DEBUGMSG
(
"[DRIVER ETH] Receiving socket for device %d created, value=%d
\n
"
,
id
,
nodes
[
id
].
socket
);
sa
.
sin_addr
.
s_addr
=
htonl
(
INADDR_ANY
);
/* We listen on all adresses */
sa
.
sin_family
=
AF_INET
;
sa
.
sin_family
=
AF_INET
;
sa
.
sin_port
=
htons
(
ip_port
);
/* Port provided by the generated code */
if
(
bind
(
nodes
[
id
].
socket
,
(
struct
sockaddr
*
)
&
sa
,
sizeof
(
struct
sockaddr_in
)
)
==
-
1
)
...
...
@@ -397,7 +397,7 @@ void __po_hi_c_driver_eth_leon_init (__po_hi_device_id id)
__DEBUGMSG
(
"[DRIVER ETH] Receiving socket listen on any address on port %d
\n
"
,
sa
.
sin_port
);
/*
/*
* Create the thread which receive all data from other
* nodes. This thread will execute the function
* __po_hi_receiver_task
...
...
@@ -405,8 +405,9 @@ void __po_hi_c_driver_eth_leon_init (__po_hi_device_id id)
__po_hi_initialize_add_task
();
__po_hi_create_generic_task
(
-
1
,
0
,
__PO_HI_MAX_PRIORITY
,
0
,
(
void
*
(
*
)(
void
))
__po_hi_c_driver_eth_leon_poller
,
NULL
);
__po_hi_create_generic_task
(
-
1
,
0
,
__PO_HI_MAX_PRIORITY
,
0
,
0
,
(
void
*
(
*
)(
void
))
__po_hi_c_driver_eth_leon_poller
,
NULL
);
// XXX Why forcing core id to 0 ?
}
/*
...
...
@@ -484,7 +485,7 @@ void __po_hi_c_driver_eth_leon_init (__po_hi_device_id id)
* second to connect on.
*/
ret
=
connect
(
nodes
[
dev
].
socket
,
ret
=
connect
(
nodes
[
dev
].
socket
,
(
struct
sockaddr
*
)
&
sa
,
sizeof
(
struct
sockaddr_in
));
...
...
@@ -543,7 +544,7 @@ void __po_hi_c_driver_eth_leon_init (__po_hi_device_id id)
#endif
#if defined (__PO_HI_NEED_DRIVER_ETH_LEON)
#if defined (__PO_HI_NEED_DRIVER_ETH_LEON)
__po_hi_msg_t
__po_hi_c_driver_eth_leon_sender_msg
;
...
...
@@ -585,7 +586,7 @@ int __po_hi_c_driver_eth_leon_sender (__po_hi_task_id task, __po_hi_port_t port
#ifdef __PO_HI_DEBUG
__DEBUGMSG
(
" [DRIVER SOCKETS] Invalid socket for port-id %d, device-id %d
\n
"
,
destination_port
,
associated_device
);
#endif
return
__PO_HI_ERROR_TRANSPORT_SEND
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
}
/*
...
...
@@ -600,7 +601,7 @@ int __po_hi_c_driver_eth_leon_sender (__po_hi_task_id task, __po_hi_port_t port
__DEBUGMSG
(
" [error getsockopt() in file %s, line%d ]
\n
"
,
__FILE__
,
__LINE__
);
close
(
nodes
[
associated_device
].
socket
);
nodes
[
associated_device
].
socket
=
-
1
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
}
if
(
optval
!=
0
)
...
...
@@ -608,7 +609,7 @@ int __po_hi_c_driver_eth_leon_sender (__po_hi_task_id task, __po_hi_port_t port
__DEBUGMSG
(
" [error getsockopt() return code in file %s, line%d ]
\n
"
,
__FILE__
,
__LINE__
);
close
(
nodes
[
associated_device
].
socket
);
nodes
[
associated_device
].
socket
=
-
1
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
}
/* Ignore SIGPIPE to be able to recover from errors instead of crashing the node */
...
...
@@ -636,12 +637,12 @@ int __po_hi_c_driver_eth_leon_sender (__po_hi_task_id task, __po_hi_port_t port
__DEBUGMSG
(
" [error write() length in file %s, line%d ]
\n
"
,
__FILE__
,
__LINE__
);
close
(
nodes
[
associated_device
].
socket
);
nodes
[
associated_device
].
socket
=
-
1
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
}
break
;
}
#endif
default:
default:
{
request
->
port
=
destination_port
;
__po_hi_msg_reallocate
(
&
__po_hi_c_driver_eth_leon_sender_msg
);
...
...
@@ -661,7 +662,7 @@ int __po_hi_c_driver_eth_leon_sender (__po_hi_task_id task, __po_hi_port_t port
__DEBUGMSG
(
" [error write() length in file %s, line%d ]
\n
"
,
__FILE__
,
__LINE__
);
close
(
nodes
[
associated_device
].
socket
);
nodes
[
associated_device
].
socket
=
-
1
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
return
__PO_HI_ERROR_TRANSPORT_SEND
;
}
request
->
port
=
__PO_HI_GQUEUE_INVALID_PORT
;
...
...
src/drivers/po_hi_driver_sockets.c
View file @
b4d60d63
...
...
@@ -5,7 +5,7 @@
*
* For more informations, please visit http://taste.tuxfamily.org/wiki
*
* Copyright (C) 2010-201
4
ESA & ISAE.
* Copyright (C) 2010-201
5
ESA & ISAE.
*/
#include
<deployment.h>
...
...
@@ -528,7 +528,8 @@ void __po_hi_driver_sockets_init (__po_hi_device_id dev_id)
__po_hi_initialize_add_task
();
__po_hi_create_generic_task
(
-
1
,
0
,
__PO_HI_MAX_PRIORITY
,
0
,
(
void
*
(
*
)(
void
))
__po_hi_sockets_poller
,
&
dev_id
);
(
-
1
,
0
,
__PO_HI_MAX_PRIORITY
,
0
,
0
,
(
void
*
(
*
)(
void
))
__po_hi_sockets_poller
,
&
dev_id
);
// XXX Why forcing core id to 0 ?
}
...
...
src/po_hi_task.c
View file @
b4d60d63
...
...
@@ -5,9 +5,22 @@
*
* For more informations, please visit http://taste.tuxfamily.org/wiki
*
* Copyright (C) 2007-2009 Telecom ParisTech, 2010-201
4
ESA & ISAE.
* Copyright (C) 2007-2009 Telecom ParisTech, 2010-201
5
ESA & ISAE.
*/
#ifdef POSIX
#ifdef __linux__
/* We need GNU extensions to support thread affinify.
These are Linux specific
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include
<sched.h>
#endif
#endif
#if defined (RTEMS_POSIX) || defined (POSIX) || defined (XENO_POSIX)
#if defined (__CYGWIN__) || defined (__MINGW32__)
#else
...
...
@@ -171,6 +184,7 @@ int __po_hi_wait_for_next_period (__po_hi_task_id task)
__PO_HI_INSTRUMENTATION_VCD_WRITE
(
"1t%d
\n
"
,
task
);
return
(
__PO_HI_SUCCESS
);
#elif defined (_WIN32)
int
ret
;
__po_hi_task_delay_until
(
&
(
tasks
[
task
].
timer
),
task
);
...
...
@@ -180,6 +194,7 @@ int __po_hi_wait_for_next_period (__po_hi_task_id task)
}
return
(
__PO_HI_SUCCESS
);
#elif defined (RTEMS_PURE)
rtems_status_code
ret
;
ret
=
rtems_rate_monotonic_period
(
tasks
[
task
].
ratemon_period
,
(
rtems_interval
)
__PO_HI_TIME_TO_US
(
tasks
[
task
].
period
)
/
rtems_configuration_get_microseconds_per_tick
());
...
...
@@ -203,6 +218,7 @@ int __po_hi_wait_for_next_period (__po_hi_task_id task)
}
return
(
__PO_HI_UNAVAILABLE
);
#elif defined (XENO_NATIVE)
unsigned
long
overrun
;
int
ret
;
...
...
@@ -250,7 +266,8 @@ int __po_hi_initialize_tasking( )
#if defined (POSIX) || defined (RTEMS_POSIX) || defined (XENO_POSIX)
pthread_t
__po_hi_posix_create_thread
(
__po_hi_priority_t
priority
,
__po_hi_stack_t
stack_size
,
void
*
(
*
start_routine
)(
void
),
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
),
void
*
arg
)
{
int
policy
;
...
...
@@ -258,11 +275,27 @@ pthread_t __po_hi_posix_create_thread (__po_hi_priority_t priority,
pthread_attr_t
attr
;
struct
sched_param
param
;
/* Create attributes to store all configuration parameters */
if
(
pthread_attr_init
(
&
attr
)
!=
0
)
{
return
((
pthread_t
)
__PO_HI_ERROR_PTHREAD_ATTR
);
}
#if defined (POSIX) && defined (__linux__)
/* Thread affinity */
cpu_set_t
cpuset
;
CPU_ZERO
(
&
cpuset
);
CPU_SET
(
core_id
,
&
cpuset
);
if
(
pthread_attr_setaffinity_np
(
&
attr
,
sizeof
(
cpuset
),
&
cpuset
)
!=
0
)
{
__DEBUGMSG
(
"CANNOT SET AFFINTY
\n
"
);
return
((
pthread_t
)
__PO_HI_ERROR_PTHREAD_ATTR
);
}
#endif
#if defined (POSIX) || defined (XENO_POSIX)
if
(
pthread_attr_setscope
(
&
attr
,
PTHREAD_SCOPE_SYSTEM
)
!=
0
)
{
...
...
@@ -273,7 +306,7 @@ pthread_t __po_hi_posix_create_thread (__po_hi_priority_t priority,
if
(
pthread_attr_setstacksize
(
&
attr
,
stack_size
)
!=
0
)
{
return
((
pthread_t
)
__PO_HI_ERROR_PTHREAD_ATTR
);
}
}
}
#elif defined (RTEMS_POSIX)
if
(
pthread_attr_setscope
(
&
attr
,
PTHREAD_SCOPE_PROCESS
)
!=
0
)
...
...
@@ -353,27 +386,46 @@ DWORD __po_hi_win32_create_thread (__po_hi_task_id id,
}
#endif
#ifdef RTEMS_PURE
rtems_id
__po_hi_rtems_create_thread
(
__po_hi_priority_t
priority
,
__po_hi_stack_t
stack_size
,
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
),
void
*
arg
)
{
rtems_id
rid
;
if
(
rtems_task_create
(
rtems_build_name
(
'T'
,
'A'
,
nb_tasks
,
' '
),
1
,
RTEMS_MINIMUM_STACK_SIZE
,
RTEMS_DEFAULT_MODES
,
RTEMS_DEFAULT_ATTRIBUTES
|
RTEMS_FLOATING_POINT
,
&
rid
)
!=
RTEMS_SUCCESSFUL
)
{
if
(
rtems_task_create
(
rtems_build_name
(
'T'
,
'A'
,
nb_tasks
,
' '
),
1
,
RTEMS_MINIMUM_STACK_SIZE
,
RTEMS_DEFAULT_MODES
,
RTEMS_DEFAULT_ATTRIBUTES
|
RTEMS_FLOATING_POINT
,
&
rid
)
!=
RTEMS_SUCCESSFUL
)
{
__DEBUGMSG
(
"ERROR when creating the task
\n
"
);
return
__PO_HI_ERROR_CREATE_TASK
;
}
}
#ifdef RTEMS411
/* Thread affinity API for SMP systems appeared in RTEMS 4.11,
section 25 of RTEMS Applications C User’s Guide */
cpu_set_t
cpuset
;
CPU_ZERO
(
&
cpuset
);
CPU_SET
(
processor_index
,
&
cpuset
);
if
(
rtems_task_set_affinity
(
task_id
,
sizeof
(
cpuset
),
&
cpuset
)
!=
RTEMS_SUCCESSFUL
)
{
__DEBUGMSG
(
"ERROR setting thread affinity
\n
"
);
return
__PO_HI_ERROR_CREATE_TASK
;
}
#endif
if
(
rtems_task_start
(
rid
,
(
rtems_task_entry
)
start_routine
,
0
)
!=
RTEMS_SUCCESSFUL
)
{
{
__DEBUGMSG
(
"ERROR when starting the task
\n
"
);
return
__PO_HI_ERROR_CREATE_TASK
;
}
}
return
rid
;
}
...
...
@@ -405,6 +457,7 @@ int __po_hi_create_generic_task (const __po_hi_task_id id,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_stack_t
stack_size
,
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
),
void
*
arg
)
{
...
...
@@ -412,7 +465,7 @@ int __po_hi_create_generic_task (const __po_hi_task_id id,
if
(
id
==
-
1
)
{
#if defined (POSIX) || defined (RTEMS_POSIX) || defined (XENO_POSIX)
__po_hi_posix_create_thread
(
priority
,
stack_size
,
start_routine
,
arg
);
__po_hi_posix_create_thread
(
priority
,
stack_size
,
core_id
,
start_routine
,
arg
);
return
(
__PO_HI_SUCCESS
);
#elif defined (_WIN32)
__po_hi_win32_create_thread
(
id
,
priority
,
stack_size
,
start_routine
,
arg
);
...
...
@@ -428,7 +481,7 @@ int __po_hi_create_generic_task (const __po_hi_task_id id,
return
(
__PO_HI_SUCCESS
);
#elif defined (RTEMS_PURE)
(
void
)
arg
;
__po_hi_rtems_create_thread
(
priority
,
stack_size
,
start_routine
,
arg
);
__po_hi_rtems_create_thread
(
priority
,
stack_size
,
core_id
,
start_routine
,
arg
);
return
(
__PO_HI_SUCCESS
);
#else
return
(
__PO_HI_UNAVAILABLE
);
...
...
@@ -441,10 +494,10 @@ int __po_hi_create_generic_task (const __po_hi_task_id id,
my_task
->
id
=
id
;
#if defined (POSIX) || defined (RTEMS_POSIX) || defined (XENO_POSIX)
my_task
->
tid
=
__po_hi_posix_create_thread
(
priority
,
stack_size
,
start_routine
,
arg
);
my_task
->
tid
=
__po_hi_posix_create_thread
(
priority
,
stack_size
,
core_id
,
start_routine
,
arg
);
__po_hi_posix_initialize_task
(
my_task
);
#elif defined (RTEMS_PURE)
my_task
->
rtems_id
=
__po_hi_rtems_create_thread
(
priority
,
stack_size
,
start_routine
,
arg
);
my_task
->
rtems_id
=
__po_hi_rtems_create_thread
(
priority
,
stack_size
,
core_id
,
start_routine
,
arg
);
#elif defined (_WIN32)
my_task
->
tid
=
__po_hi_win32_create_thread
(
id
,
priority
,
stack_size
,
start_routine
,
arg
);
#elif defined (XENO_NATIVE)
...
...
@@ -462,9 +515,10 @@ int __po_hi_create_periodic_task (const __po_hi_task_id id,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_stack_t
stack_size
,
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
))
{
if
(
__po_hi_create_generic_task
(
id
,
period
,
priority
,
stack_size
,
start_routine
,
NULL
)
!=
1
)
if
(
__po_hi_create_generic_task
(
id
,
period
,
priority
,
stack_size
,
core_id
,
start_routine
,
NULL
)
!=
1
)
{
__DEBUGMSG
(
"ERROR when creating generic task (task id=%d)
\n
"
,
id
);
return
(
__PO_HI_ERROR_CREATE_TASK
);
...
...
@@ -508,6 +562,7 @@ int __po_hi_create_sporadic_task (const __po_hi_task_id id,
const
__po_hi_time_t
*
period
,
const
__po_hi_priority_t
priority
,
const
__po_hi_stack_t
stack_size
,
const
__po_hi_int8_t
core_id
,
void
*
(
*
start_routine
)(
void
)
)
{
/*
...
...
@@ -515,7 +570,7 @@ int __po_hi_create_sporadic_task (const __po_hi_task_id id,
* last parameter. Typically, a sporadic thread will wait on a
* mutex.
*/
if
(
__po_hi_create_generic_task
(
id
,
period
,
priority
,
stack_size
,
start_routine
,
NULL
)
!=
1
)
if
(
__po_hi_create_generic_task
(
id
,
period
,
priority
,
stack_size
,
core_id
,
start_routine
,
NULL
)
!=
1
)
{
return
(
__PO_HI_ERROR_CREATE_TASK
);
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment