@@ -25,18 +25,144 @@ class noop_harq_timeout_notifier : public harq_timeout_notifier
2525 }
2626};
2727
28+ template <bool IsDl>
29+ class base_ntn_harq_history
30+ {
31+ using harq_impl_type = std::conditional_t <IsDl, dl_harq_process_impl, ul_harq_process_impl>;
32+
33+ public:
34+ base_ntn_harq_history (cell_harq_repository<IsDl>& parent_, unsigned ntn_cs_koffset_) :
35+ harq_pool (parent_), ntn_cs_koffset(ntn_cs_koffset_)
36+ {
37+ srsran_assert (ntn_cs_koffset > 0 and ntn_cs_koffset <= NTN_CELL_SPECIFIC_KOFFSET_MAX, " Invalid NTN koffset" );
38+ unsigned ring_size = get_allocator_ring_size_gt_min (ntn_cs_koffset);
39+ history.resize (ring_size);
40+ }
41+
42+ void slot_indication (slot_point sl_tx)
43+ {
44+ last_sl_ind = sl_tx;
45+ // If there are no allocations, we do not let last_sl_ack get further away from last_sl_ind, to avoid ambiguity.
46+ if (not last_sl_ack.valid () or last_sl_ack < last_sl_ind - 1 ) {
47+ last_sl_ack = last_sl_ind - 1 ;
48+ }
49+
50+ // Clear old entries.
51+ unsigned idx = get_index (sl_tx - 1 );
52+ history[idx].clear ();
53+ }
54+
55+ void save_harq_newtx_info (const harq_impl_type& h)
56+ {
57+ unsigned idx = get_index (h.slot_ack );
58+ history[idx].emplace_back (h);
59+
60+ if (h.slot_ack > last_sl_ack) {
61+ last_sl_ack = h.slot_ack ;
62+ }
63+ }
64+
65+ void save_harq_retx_info (const harq_impl_type& h, slot_point last_slot_ack)
66+ {
67+ // Clear previous HARQ transmission so it does not count in pending bytes.
68+ unsigned last_idx = get_index (last_slot_ack);
69+ for (unsigned i = 0 ; i != history[last_idx].size (); ++i) {
70+ if (history[last_idx][i].ue_idx == h.ue_idx and history[last_idx][i].h_id == h.h_id ) {
71+ history[last_idx][i].status = harq_state_t ::empty;
72+ }
73+ }
74+
75+ save_harq_newtx_info (h);
76+ }
77+
78+ protected:
79+ unsigned get_index (slot_point sl) const { return mod ((sl + ntn_cs_koffset).to_uint ()); }
80+ unsigned mod (unsigned idx) const { return idx % history.size (); }
81+
82+ cell_harq_repository<IsDl>& harq_pool;
83+ unsigned ntn_cs_koffset;
84+ std::vector<std::vector<harq_impl_type>> history;
85+
86+ slot_point last_sl_ind;
87+ slot_point last_sl_ack;
88+ };
89+
2890} // namespace
2991
92+ namespace srsran ::harq_utils {
93+
94+ // / Handles the DL HARQ information in the case NTN mode.
95+ class ntn_dl_harq_alloc_history : public base_ntn_harq_history <true >
96+ {
97+ public:
98+ using base_ntn_harq_history<true >::base_ntn_harq_history;
99+
100+ std::optional<dl_harq_process_handle> find_dl_harq (du_ue_index_t ue_idx, slot_point uci_slot, unsigned harq_bit_idx)
101+ {
102+ unsigned idx = get_index (uci_slot);
103+ for (dl_harq_process_impl& h_impl : history[idx]) {
104+ if (h_impl.ue_idx == ue_idx and h_impl.status == harq_state_t ::waiting_ack and h_impl.slot_ack == uci_slot and
105+ h_impl.harq_bit_idx == harq_bit_idx) {
106+ return dl_harq_process_handle{harq_pool, h_impl};
107+ }
108+ }
109+ return std::nullopt ;
110+ }
111+ };
112+
113+ // / Handles the UL HARQ information in the case NTN mode.
114+ class ntn_ul_harq_alloc_history : public base_ntn_harq_history <false >
115+ {
116+ public:
117+ using base_ntn_harq_history<false >::base_ntn_harq_history;
118+
119+ std::optional<ul_harq_process_handle> find_ul_harq (du_ue_index_t ue_idx, slot_point pusch_slot)
120+ {
121+ unsigned idx = get_index (pusch_slot);
122+ for (ul_harq_process_impl& h_impl : history[idx]) {
123+ if (h_impl.ue_idx == ue_idx and h_impl.status == harq_state_t ::waiting_ack and h_impl.slot_ack == pusch_slot) {
124+ return ul_harq_process_handle{harq_pool, h_impl};
125+ }
126+ }
127+ return std::nullopt ;
128+ }
129+
130+ unsigned sum_pending_ul_tbs (du_ue_index_t ue_idx) const
131+ {
132+ if (last_sl_ack < last_sl_ind) {
133+ return 0 ;
134+ }
135+ unsigned sum = 0 ;
136+ for (unsigned idx = get_index (last_sl_ind), last_idx = get_index (last_sl_ack + 1 ); idx != last_idx;
137+ idx = mod (idx + 1 )) {
138+ for (const ul_harq_process_impl& h : history[idx]) {
139+ if (h.ue_idx == ue_idx and h.status != harq_state_t ::empty) {
140+ sum += h.prev_tx_params .tbs_bytes ;
141+ break ;
142+ }
143+ }
144+ }
145+ return sum;
146+ }
147+ };
148+
149+ } // namespace srsran::harq_utils
150+
151+ // In NTN case, we timeout the HARQ since we need to reuse the process before the PUSCH arrives.
152+ static const unsigned NTN_ACK_WAIT_TIMEOUT = 1 ;
153+
30154template <bool IsDl>
31155cell_harq_repository<IsDl>::cell_harq_repository(unsigned max_ues,
32156 unsigned max_ack_wait_timeout,
33157 unsigned max_harqs_per_ue_,
158+ unsigned ntn_cs_koffset,
34159 harq_timeout_notifier& timeout_notifier_,
35160 srslog::basic_logger& logger_) :
36- max_ack_wait_in_slots (max_ack_wait_timeout),
161+ max_ack_wait_in_slots (ntn_cs_koffset == 0 ? max_ack_wait_timeout : NTN_ACK_WAIT_TIMEOUT ),
37162 max_harqs_per_ue(max_harqs_per_ue_),
38163 timeout_notifier(timeout_notifier_),
39- logger(logger_)
164+ logger(logger_),
165+ alloc_hist(ntn_cs_koffset > 0 ? std::make_unique<harq_alloc_history>(*this , ntn_cs_koffset) : nullptr)
40166{
41167 // Reserve space in advance for UEs and their HARQs.
42168 ues.resize (max_ues);
@@ -48,6 +174,11 @@ cell_harq_repository<IsDl>::cell_harq_repository(unsigned max_ues,
48174 harq_timeout_wheel.resize (get_allocator_ring_size_gt_min (max_ack_wait_timeout + get_max_slot_ul_alloc_delay (0 )));
49175}
50176
177+ template <bool IsDl>
178+ cell_harq_repository<IsDl>::~cell_harq_repository ()
179+ {
180+ }
181+
51182template <bool IsDl>
52183void cell_harq_repository<IsDl>::slot_indication(slot_point sl_tx)
53184{
@@ -147,6 +278,11 @@ typename cell_harq_repository<IsDl>::harq_type* cell_harq_repository<IsDl>::allo
147278 h.slot_ack_timeout = sl_ack + max_ack_wait_in_slots;
148279 harq_timeout_wheel[h.slot_ack_timeout .to_uint () % harq_timeout_wheel.size ()].push_front (&h);
149280
281+ if (is_ntn_mode ()) {
282+ // In NTN mode, save HARQ info separately.
283+ alloc_hist->save_harq_newtx_info (h);
284+ }
285+
150286 return &h;
151287}
152288
@@ -195,6 +331,11 @@ void cell_harq_repository<IsDl>::handle_ack(harq_type& h, bool ack)
195331 }
196332 }
197333
334+ if (is_ntn_mode ()) {
335+ // In NTN mode, ACK info does not affect the timeout/retx containers.
336+ return ;
337+ }
338+
198339 if (ack or h.nof_retxs >= h.max_nof_harq_retxs ) {
199340 // If the HARQ process is ACKed or the maximum number of retransmissions has been reached, we can deallocate the
200341 // HARQ process.
@@ -234,15 +375,22 @@ bool cell_harq_repository<IsDl>::handle_new_retx(harq_type& h, slot_point sl_tx,
234375 // Remove HARQ from pending Retx list.
235376 harq_pending_retx_list.pop (&h);
236377
237- h.status = harq_state_t ::waiting_ack;
238- h.slot_tx = sl_tx;
239- h.slot_ack = sl_ack;
240- h.ack_on_timeout = false ;
378+ slot_point prev_sl_ack = h.slot_ack ;
379+ h.status = harq_state_t ::waiting_ack;
380+ h.slot_tx = sl_tx;
381+ h.slot_ack = sl_ack;
382+ h.ack_on_timeout = false ;
241383 h.nof_retxs ++;
242384
243385 // Add HARQ to the timeout list.
244386 h.slot_ack_timeout = sl_ack + max_ack_wait_in_slots;
245387 harq_timeout_wheel[h.slot_ack_timeout .to_uint () % harq_timeout_wheel.size ()].push_front (&h);
388+
389+ if (is_ntn_mode ()) {
390+ // In NTN mode, save HARQ info separately.
391+ alloc_hist->save_harq_retx_info (h, prev_sl_ack);
392+ }
393+
246394 return true ;
247395}
248396
@@ -301,6 +449,12 @@ cell_harq_repository<IsDl>::find_ue_harq_in_state(du_ue_index_t ue_idx, harq_uti
301449 return nullptr ;
302450}
303451
452+ template <bool IsDl>
453+ bool cell_harq_repository<IsDl>::is_ntn_mode() const
454+ {
455+ return alloc_hist != nullptr ;
456+ }
457+
304458template <bool IsDl>
305459typename cell_harq_repository<IsDl>::harq_type*
306460cell_harq_repository<IsDl>::find_ue_harq_in_state(du_ue_index_t ue_idx, harq_utils::harq_state_t state)
@@ -336,12 +490,14 @@ template class harq_utils::base_harq_process_handle<false>;
336490cell_harq_manager::cell_harq_manager (unsigned max_ues,
337491 unsigned max_harqs_per_ue_,
338492 std::unique_ptr<harq_timeout_notifier> notifier,
339- unsigned max_ack_wait_timeout) :
493+ unsigned max_ack_wait_timeout,
494+ unsigned ntn_cs_koffset) :
340495 max_harqs_per_ue(max_harqs_per_ue_),
341- timeout_notifier(notifier != nullptr ? std::move(notifier) : std::make_unique<noop_harq_timeout_notifier>()),
496+ timeout_notifier(notifier != nullptr and ntn_cs_koffset == 0 ? std::move(notifier)
497+ : std::make_unique<noop_harq_timeout_notifier>()),
342498 logger(srslog::fetch_basic_logger(" SCHED" )),
343- dl(max_ues, max_ack_wait_timeout, max_harqs_per_ue, *timeout_notifier, logger),
344- ul(max_ues, max_ack_wait_timeout, max_harqs_per_ue, *timeout_notifier, logger)
499+ dl(max_ues, max_ack_wait_timeout, max_harqs_per_ue, ntn_cs_koffset, *timeout_notifier, logger),
500+ ul(max_ues, max_ack_wait_timeout, max_harqs_per_ue, ntn_cs_koffset, *timeout_notifier, logger)
345501{
346502}
347503
@@ -463,6 +619,12 @@ dl_harq_process_handle::status_update dl_harq_process_handle::dl_ack_info(mac_ha
463619 // Case: This is not the last PUCCH HARQ-ACK that is expected for this HARQ process.
464620 impl->pucch_ack_to_receive --;
465621 impl->ack_on_timeout = impl->chosen_ack == mac_harq_ack_report_status::ack;
622+
623+ if (harq_repo->is_ntn_mode ()) {
624+ // Timeouts don't need to be updated in NTN mode.
625+ return status_update::no_update;
626+ }
627+
466628 // We reduce the HARQ process timeout to receive the next HARQ-ACK. This is done because the two HARQ-ACKs should
467629 // arrive almost simultaneously, and in case the second goes missing, we don't want to block the HARQ for too long.
468630 auto & wheel = harq_repo->harq_timeout_wheel ;
@@ -679,6 +841,11 @@ std::optional<ul_harq_process_handle> unique_ue_harq_entity::find_ul_harq_waitin
679841
680842std::optional<dl_harq_process_handle> unique_ue_harq_entity::find_dl_harq (slot_point uci_slot, uint8_t harq_bit_idx)
681843{
844+ if (cell_harq_mgr->dl .alloc_hist != nullptr ) {
845+ // NTN mode.
846+ return cell_harq_mgr->dl .alloc_hist ->find_dl_harq (ue_index, uci_slot, harq_bit_idx);
847+ }
848+
682849 std::vector<dl_harq_process_impl>& dl_harqs = cell_harq_mgr->dl .ues [ue_index].harqs ;
683850 for (dl_harq_process_impl& h : dl_harqs) {
684851 if (h.status == harq_utils::harq_state_t ::waiting_ack and h.slot_ack == uci_slot and
@@ -691,6 +858,11 @@ std::optional<dl_harq_process_handle> unique_ue_harq_entity::find_dl_harq(slot_p
691858
692859std::optional<ul_harq_process_handle> unique_ue_harq_entity::find_ul_harq (slot_point pusch_slot)
693860{
861+ if (cell_harq_mgr->ul .alloc_hist != nullptr ) {
862+ // NTN mode.
863+ return cell_harq_mgr->ul .alloc_hist ->find_ul_harq (ue_index, pusch_slot);
864+ }
865+
694866 std::vector<ul_harq_process_impl>& ul_harqs = cell_harq_mgr->ul .ues [ue_index].harqs ;
695867 for (ul_harq_process_impl& h : ul_harqs) {
696868 if (h.status == harq_utils::harq_state_t ::waiting_ack and h.slot_tx == pusch_slot) {
@@ -713,3 +885,19 @@ void unique_ue_harq_entity::uci_sched_failed(slot_point uci_slot)
713885 }
714886 }
715887}
888+
889+ unsigned unique_ue_harq_entity::total_ul_bytes_waiting_crc () const
890+ {
891+ if (cell_harq_mgr->ul .is_ntn_mode ()) {
892+ return cell_harq_mgr->ul .alloc_hist ->sum_pending_ul_tbs (ue_index);
893+ }
894+
895+ unsigned harq_bytes = 0 ;
896+ for (unsigned i = 0 ; i != nof_ul_harqs (); ++i) {
897+ if (get_ul_ue ().harqs [i].status != harq_utils::harq_state_t ::empty) {
898+ harq_bytes += get_ul_ue ().harqs [i].prev_tx_params .tbs_bytes ;
899+ }
900+ }
901+
902+ return harq_bytes;
903+ }
0 commit comments