Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-RAN
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
常顺宇
OpenXG-RAN
Commits
77c7fe5b
Commit
77c7fe5b
authored
Dec 21, 2020
by
Robert Schmidt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create more generic NR_list_t with helper functions
parent
338d8deb
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
110 additions
and
34 deletions
+110
-34
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
+1
-1
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
+86
-16
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
+2
-2
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
+1
-1
openair2/LAYER2/NR_MAC_gNB/mac_proto.h
openair2/LAYER2/NR_MAC_gNB/mac_proto.h
+9
-2
openair2/LAYER2/NR_MAC_gNB/main.c
openair2/LAYER2/NR_MAC_gNB/main.c
+1
-2
openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
+10
-10
No files found.
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
View file @
77c7fe5b
...
@@ -512,7 +512,7 @@ void nr_schedule_ue_spec(module_id_t module_id,
...
@@ -512,7 +512,7 @@ void nr_schedule_ue_spec(module_id_t module_id,
nfapi_nr_dl_tti_request_body_t
*
dl_req
=
&
gNB_mac
->
DL_req
[
CC_id
].
dl_tti_request_body
;
nfapi_nr_dl_tti_request_body_t
*
dl_req
=
&
gNB_mac
->
DL_req
[
CC_id
].
dl_tti_request_body
;
NR_
UE_
list_t
*
UE_list
=
&
UE_info
->
list
;
NR_list_t
*
UE_list
=
&
UE_info
->
list
;
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
...
...
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
View file @
77c7fe5b
...
@@ -1442,35 +1442,105 @@ int extract_length(int startSymbolAndLength) {
...
@@ -1442,35 +1442,105 @@ int extract_length(int startSymbolAndLength) {
/*
/*
* Dump the UL or DL UE_info into LOG_T(MAC)
* Dump the UL or DL UE_info into LOG_T(MAC)
*/
*/
void
dump_nr_ue_list
(
NR_UE_list_t
*
listP
)
{
void
dump_nr_list
(
NR_list_t
*
listP
)
{
for
(
int
j
=
listP
->
head
;
j
>=
0
;
j
=
listP
->
next
[
j
])
for
(
int
j
=
listP
->
head
;
j
>=
0
;
j
=
listP
->
next
[
j
])
LOG_T
(
MAC
,
"DL list node %d => %d
\n
"
,
j
,
listP
->
next
[
j
]);
LOG_T
(
MAC
,
"NR list node %d => %d
\n
"
,
j
,
listP
->
next
[
j
]);
}
/*
* Create a new NR_list
*/
void
create_nr_list
(
NR_list_t
*
list
,
int
len
)
{
list
->
head
=
-
1
;
list
->
next
=
calloc
(
len
,
sizeof
(
*
list
->
next
));
AssertFatal
(
list
,
"cannot calloc() memory for NR_list_t->next
\n
"
);
for
(
int
i
=
0
;
i
<
len
;
++
i
)
list
->
next
[
i
]
=
-
1
;
list
->
tail
=
-
1
;
list
->
len
=
len
;
}
}
/*
/*
*
Add a UE to NR_UE_list listP
*
Destroy an NR_list
*/
*/
inline
void
add_nr_ue_list
(
NR_UE_list_t
*
listP
,
int
UE_id
)
{
void
destroy_nr_list
(
NR_list_t
*
list
)
{
free
(
list
->
next
);
}
/*
* Add an ID to an NR_list at the end, traversing the whole list. Note:
* add_tail_nr_list() is a faster alternative, but this implementation ensures
* we do not add an existing ID.
*/
void
add_nr_list
(
NR_list_t
*
listP
,
int
id
)
{
int
*
cur
=
&
listP
->
head
;
int
*
cur
=
&
listP
->
head
;
while
(
*
cur
>=
0
)
{
while
(
*
cur
>=
0
)
{
AssertFatal
(
*
cur
!=
UE_id
,
"UE_id %d already in NR_UE_list!
\n
"
,
UE_
id
);
AssertFatal
(
*
cur
!=
id
,
"id %d already in NR_UE_list!
\n
"
,
id
);
cur
=
&
listP
->
next
[
*
cur
];
cur
=
&
listP
->
next
[
*
cur
];
}
}
*
cur
=
UE_id
;
*
cur
=
id
;
if
(
listP
->
next
[
id
]
<
0
)
listP
->
tail
=
id
;
}
}
/*
/*
* Remove a
UE from NR_UE_list listP
* Remove a
n ID from an NR_list
*/
*/
static
inline
void
remove_nr_ue_list
(
NR_UE_list_t
*
listP
,
int
UE_id
)
{
void
remove_nr_list
(
NR_list_t
*
listP
,
int
id
)
{
int
*
cur
=
&
listP
->
head
;
int
*
cur
=
&
listP
->
head
;
while
(
*
cur
!=
-
1
&&
*
cur
!=
UE_id
)
int
*
prev
=
&
listP
->
head
;
while
(
*
cur
!=
-
1
&&
*
cur
!=
id
)
{
prev
=
cur
;
cur
=
&
listP
->
next
[
*
cur
];
cur
=
&
listP
->
next
[
*
cur
];
AssertFatal
(
*
cur
!=
-
1
,
"UE %d not found in UE_list
\n
"
,
UE_id
);
}
AssertFatal
(
*
cur
!=
-
1
,
"ID %d not found in UE_list
\n
"
,
id
);
int
*
next
=
&
listP
->
next
[
*
cur
];
int
*
next
=
&
listP
->
next
[
*
cur
];
*
cur
=
listP
->
next
[
*
cur
];
*
cur
=
listP
->
next
[
*
cur
];
*
next
=
-
1
;
*
next
=
-
1
;
return
;
listP
->
tail
=
*
prev
>=
0
&&
listP
->
next
[
*
prev
]
>=
0
?
listP
->
tail
:
*
prev
;
}
/*
* Add an ID to the tail of the NR_list in O(1). Note that there is
* corresponding remove_tail_nr_list(), as we cannot set the tail backwards and
* therefore need to go through the whole list (use remove_nr_list())
*/
void
add_tail_nr_list
(
NR_list_t
*
listP
,
int
id
)
{
int
*
last
=
listP
->
tail
<
0
?
&
listP
->
head
:
&
listP
->
next
[
listP
->
tail
];
*
last
=
id
;
listP
->
next
[
id
]
=
-
1
;
listP
->
tail
=
id
;
}
/*
* Add an ID to the front of the NR_list in O(1)
*/
void
add_front_nr_list
(
NR_list_t
*
listP
,
int
id
)
{
const
int
ohead
=
listP
->
head
;
listP
->
head
=
id
;
listP
->
next
[
id
]
=
ohead
;
if
(
listP
->
tail
<
0
)
listP
->
tail
=
id
;
}
/*
* Remove an ID from the front of the NR_list in O(1)
*/
void
remove_front_nr_list
(
NR_list_t
*
listP
)
{
AssertFatal
(
listP
->
head
>=
0
,
"Nothing to remove
\n
"
);
const
int
ohead
=
listP
->
head
;
listP
->
head
=
listP
->
next
[
ohead
];
listP
->
next
[
ohead
]
=
-
1
;
if
(
listP
->
head
<
0
)
listP
->
tail
=
-
1
;
}
}
int
find_nr_UE_id
(
module_id_t
mod_idP
,
rnti_t
rntiP
)
int
find_nr_UE_id
(
module_id_t
mod_idP
,
rnti_t
rntiP
)
...
@@ -1536,7 +1606,7 @@ int add_new_nr_ue(module_id_t mod_idP, rnti_t rntiP){
...
@@ -1536,7 +1606,7 @@ int add_new_nr_ue(module_id_t mod_idP, rnti_t rntiP){
mod_idP
,
mod_idP
,
rntiP
,
rntiP
,
UE_info
->
num_UEs
);
UE_info
->
num_UEs
);
dump_nr_
ue_
list
(
&
UE_info
->
list
);
dump_nr_list
(
&
UE_info
->
list
);
for
(
int
i
=
0
;
i
<
MAX_MOBILES_PER_GNB
;
i
++
)
{
for
(
int
i
=
0
;
i
<
MAX_MOBILES_PER_GNB
;
i
++
)
{
if
(
UE_info
->
active
[
i
])
if
(
UE_info
->
active
[
i
])
...
@@ -1546,7 +1616,7 @@ int add_new_nr_ue(module_id_t mod_idP, rnti_t rntiP){
...
@@ -1546,7 +1616,7 @@ int add_new_nr_ue(module_id_t mod_idP, rnti_t rntiP){
UE_info
->
num_UEs
++
;
UE_info
->
num_UEs
++
;
UE_info
->
active
[
UE_id
]
=
true
;
UE_info
->
active
[
UE_id
]
=
true
;
UE_info
->
rnti
[
UE_id
]
=
rntiP
;
UE_info
->
rnti
[
UE_id
]
=
rntiP
;
add_nr_
ue_
list
(
&
UE_info
->
list
,
UE_id
);
add_nr_list
(
&
UE_info
->
list
,
UE_id
);
set_Y
(
UE_info
->
Y
[
UE_id
],
rntiP
);
set_Y
(
UE_info
->
Y
[
UE_id
],
rntiP
);
memset
((
void
*
)
&
UE_info
->
UE_sched_ctrl
[
UE_id
],
memset
((
void
*
)
&
UE_info
->
UE_sched_ctrl
[
UE_id
],
0
,
0
,
...
@@ -1561,13 +1631,13 @@ int add_new_nr_ue(module_id_t mod_idP, rnti_t rntiP){
...
@@ -1561,13 +1631,13 @@ int add_new_nr_ue(module_id_t mod_idP, rnti_t rntiP){
mod_idP
,
mod_idP
,
UE_id
,
UE_id
,
rntiP
);
rntiP
);
dump_nr_
ue_
list
(
&
UE_info
->
list
);
dump_nr_list
(
&
UE_info
->
list
);
return
(
UE_id
);
return
(
UE_id
);
}
}
// printf("MAC: cannot add new UE for rnti %x\n", rntiP);
// printf("MAC: cannot add new UE for rnti %x\n", rntiP);
LOG_E
(
MAC
,
"error in add_new_ue(), could not find space in UE_info, Dumping UE list
\n
"
);
LOG_E
(
MAC
,
"error in add_new_ue(), could not find space in UE_info, Dumping UE list
\n
"
);
dump_nr_
ue_
list
(
&
UE_info
->
list
);
dump_nr_list
(
&
UE_info
->
list
);
return
-
1
;
return
-
1
;
}
}
...
@@ -1595,7 +1665,7 @@ void mac_remove_nr_ue(module_id_t mod_id, rnti_t rnti)
...
@@ -1595,7 +1665,7 @@ void mac_remove_nr_ue(module_id_t mod_id, rnti_t rnti)
UE_info
->
num_UEs
--
;
UE_info
->
num_UEs
--
;
UE_info
->
active
[
UE_id
]
=
FALSE
;
UE_info
->
active
[
UE_id
]
=
FALSE
;
UE_info
->
rnti
[
UE_id
]
=
0
;
UE_info
->
rnti
[
UE_id
]
=
0
;
remove_nr_
ue_
list
(
&
UE_info
->
list
,
UE_id
);
remove_nr_list
(
&
UE_info
->
list
,
UE_id
);
memset
((
void
*
)
&
UE_info
->
UE_sched_ctrl
[
UE_id
],
memset
((
void
*
)
&
UE_info
->
UE_sched_ctrl
[
UE_id
],
0
,
0
,
sizeof
(
NR_UE_sched_ctrl_t
));
sizeof
(
NR_UE_sched_ctrl_t
));
...
...
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
View file @
77c7fe5b
...
@@ -83,7 +83,7 @@ void nr_schedule_pucch(int Mod_idP,
...
@@ -83,7 +83,7 @@ void nr_schedule_pucch(int Mod_idP,
frame_t
frameP
,
frame_t
frameP
,
sub_frame_t
slotP
)
{
sub_frame_t
slotP
)
{
NR_UE_info_t
*
UE_info
=
&
RC
.
nrmac
[
Mod_idP
]
->
UE_info
;
NR_UE_info_t
*
UE_info
=
&
RC
.
nrmac
[
Mod_idP
]
->
UE_info
;
const
NR_
UE_
list_t
*
UE_list
=
&
UE_info
->
list
;
const
NR_list_t
*
UE_list
=
&
UE_info
->
list
;
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
...
@@ -198,7 +198,7 @@ void nr_csi_meas_reporting(int Mod_idP,
...
@@ -198,7 +198,7 @@ void nr_csi_meas_reporting(int Mod_idP,
const
int
n_slots_frame
=
nr_slots_per_frame
[
*
scc
->
ssbSubcarrierSpacing
];
const
int
n_slots_frame
=
nr_slots_per_frame
[
*
scc
->
ssbSubcarrierSpacing
];
NR_UE_info_t
*
UE_info
=
&
RC
.
nrmac
[
Mod_idP
]
->
UE_info
;
NR_UE_info_t
*
UE_info
=
&
RC
.
nrmac
[
Mod_idP
]
->
UE_info
;
NR_
UE_
list_t
*
UE_list
=
&
UE_info
->
list
;
NR_list_t
*
UE_list
=
&
UE_info
->
list
;
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
const
NR_CellGroupConfig_t
*
secondaryCellGroup
=
UE_info
->
secondaryCellGroup
[
UE_id
];
const
NR_CellGroupConfig_t
*
secondaryCellGroup
=
UE_info
->
secondaryCellGroup
[
UE_id
];
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
...
...
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
View file @
77c7fe5b
...
@@ -607,7 +607,7 @@ void nr_schedule_ulsch(module_id_t module_id,
...
@@ -607,7 +607,7 @@ void nr_schedule_ulsch(module_id_t module_id,
NR_ServingCellConfigCommon_t
*
scc
=
RC
.
nrmac
[
module_id
]
->
common_channels
[
0
].
ServingCellConfigCommon
;
NR_ServingCellConfigCommon_t
*
scc
=
RC
.
nrmac
[
module_id
]
->
common_channels
[
0
].
ServingCellConfigCommon
;
NR_UE_info_t
*
UE_info
=
&
RC
.
nrmac
[
module_id
]
->
UE_info
;
NR_UE_info_t
*
UE_info
=
&
RC
.
nrmac
[
module_id
]
->
UE_info
;
const
NR_
UE_
list_t
*
UE_list
=
&
UE_info
->
list
;
const
NR_list_t
*
UE_list
=
&
UE_info
->
list
;
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
for
(
int
UE_id
=
UE_list
->
head
;
UE_id
>=
0
;
UE_id
=
UE_list
->
next
[
UE_id
])
{
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
NR_UE_sched_ctrl_t
*
sched_ctrl
=
&
UE_info
->
UE_sched_ctrl
[
UE_id
];
/* dynamic PUSCH values (RB alloc, MCS, hence R, Qm, TBS) that change in
/* dynamic PUSCH values (RB alloc, MCS, hence R, Qm, TBS) that change in
...
...
openair2/LAYER2/NR_MAC_gNB/mac_proto.h
View file @
77c7fe5b
...
@@ -293,8 +293,15 @@ int NRRIV2BW(int locationAndBandwidth,int N_RB);
...
@@ -293,8 +293,15 @@ int NRRIV2BW(int locationAndBandwidth,int N_RB);
int
NRRIV2PRBOFFSET
(
int
locationAndBandwidth
,
int
N_RB
);
int
NRRIV2PRBOFFSET
(
int
locationAndBandwidth
,
int
N_RB
);
void
dump_nr_ue_list
(
NR_UE_list_t
*
listP
);
/* Functions to manage an NR_list_t */
void
add_nr_ue_list
(
NR_UE_list_t
*
listP
,
int
UE_id
);
void
dump_nr_list
(
NR_list_t
*
listP
);
void
create_nr_list
(
NR_list_t
*
listP
,
int
len
);
void
destroy_nr_list
(
NR_list_t
*
list
);
void
add_nr_list
(
NR_list_t
*
listP
,
int
id
);
void
remove_nr_list
(
NR_list_t
*
listP
,
int
id
);
void
add_tail_nr_list
(
NR_list_t
*
listP
,
int
id
);
void
add_front_nr_list
(
NR_list_t
*
listP
,
int
id
);
void
remove_front_nr_list
(
NR_list_t
*
listP
);
int
find_nr_UE_id
(
module_id_t
mod_idP
,
rnti_t
rntiP
);
int
find_nr_UE_id
(
module_id_t
mod_idP
,
rnti_t
rntiP
);
...
...
openair2/LAYER2/NR_MAC_gNB/main.c
View file @
77c7fe5b
...
@@ -113,9 +113,8 @@ void mac_top_init_gNB(void)
...
@@ -113,9 +113,8 @@ void mac_top_init_gNB(void)
UE_info
=
&
nrmac
->
UE_info
;
UE_info
=
&
nrmac
->
UE_info
;
UE_info
->
num_UEs
=
0
;
UE_info
->
num_UEs
=
0
;
UE_info
->
list
.
head
=
-
1
;
create_nr_list
(
&
UE_info
->
list
,
MAX_MOBILES_PER_GNB
)
;
for
(
list_el
=
0
;
list_el
<
MAX_MOBILES_PER_GNB
;
list_el
++
)
{
for
(
list_el
=
0
;
list_el
<
MAX_MOBILES_PER_GNB
;
list_el
++
)
{
UE_info
->
list
.
next
[
list_el
]
=
-
1
;
UE_info
->
active
[
list_el
]
=
false
;
UE_info
->
active
[
list_el
]
=
false
;
for
(
int
list_harq
=
0
;
list_harq
<
NR_MAX_NB_HARQ_PROCESSES
;
list_harq
++
)
{
for
(
int
list_harq
=
0
;
list_harq
<
NR_MAX_NB_HARQ_PROCESSES
;
list_harq
++
)
{
UE_info
->
UE_sched_ctrl
[
list_el
].
harq_processes
[
list_harq
].
round
=
0
;
UE_info
->
UE_sched_ctrl
[
list_el
].
harq_processes
[
list_harq
].
round
=
0
;
...
...
openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
View file @
77c7fe5b
...
@@ -79,6 +79,15 @@
...
@@ -79,6 +79,15 @@
#define NR_NB_RA_PROC_MAX 4
#define NR_NB_RA_PROC_MAX 4
#define MAX_NUM_OF_SSB 64
#define MAX_NUM_OF_SSB 64
/*! \brief NR_list_t is a "list" (of users, HARQ processes, slices, ...).
* Especially useful in the scheduler and to keep "classes" of users. */
typedef
struct
{
int
head
;
int
*
next
;
int
tail
;
int
len
;
}
NR_list_t
;
typedef
enum
{
typedef
enum
{
RA_IDLE
=
0
,
RA_IDLE
=
0
,
Msg2
=
1
,
Msg2
=
1
,
...
@@ -447,15 +456,6 @@ typedef struct {
...
@@ -447,15 +456,6 @@ typedef struct {
}
NR_mac_stats_t
;
}
NR_mac_stats_t
;
/*! \brief UNR_E_list_t is a "list" of users within UE_info_t. Especial useful in
* the scheduler and to keep "classes" of users. */
typedef
struct
{
int
head
;
int
next
[
MAX_MOBILES_PER_GNB
];
}
NR_UE_list_t
;
/*! \brief UE list used by gNB to order UEs/CC for scheduling*/
/*! \brief UE list used by gNB to order UEs/CC for scheduling*/
#define MAX_CSI_REPORTCONFIG 48
#define MAX_CSI_REPORTCONFIG 48
typedef
struct
{
typedef
struct
{
...
@@ -463,7 +463,7 @@ typedef struct {
...
@@ -463,7 +463,7 @@ typedef struct {
nr_csi_report_t
csi_report_template
[
MAX_MOBILES_PER_GNB
][
MAX_CSI_REPORTCONFIG
];
nr_csi_report_t
csi_report_template
[
MAX_MOBILES_PER_GNB
][
MAX_CSI_REPORTCONFIG
];
NR_UE_sched_ctrl_t
UE_sched_ctrl
[
MAX_MOBILES_PER_GNB
];
NR_UE_sched_ctrl_t
UE_sched_ctrl
[
MAX_MOBILES_PER_GNB
];
NR_mac_stats_t
mac_stats
[
MAX_MOBILES_PER_GNB
];
NR_mac_stats_t
mac_stats
[
MAX_MOBILES_PER_GNB
];
NR_
UE_
list_t
list
;
NR_list_t
list
;
int
num_UEs
;
int
num_UEs
;
bool
active
[
MAX_MOBILES_PER_GNB
];
bool
active
[
MAX_MOBILES_PER_GNB
];
...
...
Write
Preview
Markdown
is supported
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