diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container
index 118eb844d49855cbbee175e7c29b6deb93fba8ed..ebb2fbd5e0aebd2a9a45206e3954912db62b19dd 100644
--- a/ci-scripts/Jenkinsfile-GitLab-Container
+++ b/ci-scripts/Jenkinsfile-GitLab-Container
@@ -425,28 +425,26 @@ pipeline {
             }
           }
         }
-
-        //avra is offline, re-enable once it is available
-        //stage ("T1-Offload-Test") {
-        //  when { expression {do5Gtest} }
-        //  steps {
-        //    script {
-        //      triggerSlaveJob ('RAN-T1-Offload-Test', 'T1-Offload-Test')
-        //    }
-        //  }
-        //  post {
-        //    always {
-        //      script {
-        //        finalizeSlaveJob('RAN-T1-Offload-Test')
-        //      }
-        //    }
-        //    failure {
-        //      script {
-        //        currentBuild.result = 'FAILURE'
-        //      }
-        //    }
-        //  }
-        //}
+        stage ("Interop-F1") {
+          when { expression {do5Gtest} }
+          steps {
+            script {
+              triggerSlaveJob ('RAN-Interop-F1', 'Interop-F1')
+            }
+          }
+          post {
+            always {
+              script {
+                finalizeSlaveJob('RAN-Interop-F1')
+              }
+            }
+            failure {
+              script {
+                currentBuild.result = 'FAILURE'
+              }
+            }
+          }
+        }
       }
     }
     stage ("DockerHub-Push") {
diff --git a/ci-scripts/xml_files/container_5g_rfsim_f1_accelleran.xml b/ci-scripts/xml_files/container_5g_rfsim_f1_accelleran.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d727fdfd1e1f342a5dfbea13a4d8e5fc12cadd05
--- /dev/null
+++ b/ci-scripts/xml_files/container_5g_rfsim_f1_accelleran.xml
@@ -0,0 +1,74 @@
+<!--
+
+ Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The OpenAirInterface Software Alliance licenses this file to You under
+ the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.openairinterface.org/?page_id=698
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ For more information about the OpenAirInterface (OAI) Software Alliance:
+      contact@openairinterface.org
+
+-->
+<testCaseList>
+        <htmlTabRef>rfsim-5gnr-f1-accelleran</htmlTabRef>
+        <htmlTabName>F1 DU Interop Accelleran CU</htmlTabName>
+        <htmlTabIcon>wrench</htmlTabIcon>
+        <TestCaseRequestedList>
+ 111111
+ 000020
+ 000021
+ 020021
+ 100021
+ 100022
+        </TestCaseRequestedList>
+        <TestCaseExclusionList></TestCaseExclusionList>
+
+        <testCase id="111111">
+                <class>Pull_Local_Registry</class>
+                <desc>Pull Images from Local Registry</desc>
+                <test_svr_id>0</test_svr_id>
+                <images_to_pull>oai-gnb oai-nr-ue</images_to_pull>
+        </testCase>
+
+        <testCase id="000020">
+                <class>DeployGenObject</class>
+                <desc>Deploy OAI-DU</desc>
+                <yaml_path>yaml_files/5g_rfsimulator_accelleran</yaml_path>
+                <services>oai-du</services>
+                <nb_healthy>1</nb_healthy>
+        </testCase>
+
+        <testCase id="000021">
+                <class>DeployGenObject</class>
+                <desc>Deploy nrUE</desc>
+                <yaml_path>yaml_files/5g_rfsimulator_accelleran</yaml_path>
+                <services>oai-nr-ue</services>
+                <nb_healthy>2</nb_healthy>
+        </testCase>
+
+        <testCase id="020021">
+                <class>PingFromContainer</class>
+                <desc>Test Connectivity (ping)</desc>
+                <container_name>rfsim5g-oai-nr-ue</container_name>
+                <options>-I oaitun_ue1 -c 20 12.1.1.1</options>
+                <loss_threshold>0</loss_threshold>
+        </testCase>
+
+        <testCase id="100022">
+                <class>UndeployGenObject</class>
+                <desc>Undeploy all OAI 5G stack</desc>
+                <yaml_path>yaml_files/5g_rfsimulator_accelleran</yaml_path>
+        </testCase>
+
+</testCaseList>
diff --git a/ci-scripts/xml_files/container_5g_rfsim_f1_accelleran_down.xml b/ci-scripts/xml_files/container_5g_rfsim_f1_accelleran_down.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0a4f9fcbfaa87cc8859b49603d47abec1df9c079
--- /dev/null
+++ b/ci-scripts/xml_files/container_5g_rfsim_f1_accelleran_down.xml
@@ -0,0 +1,45 @@
+<!--
+
+ Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The OpenAirInterface Software Alliance licenses this file to You under
+ the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.openairinterface.org/?page_id=698
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ For more information about the OpenAirInterface (OAI) Software Alliance:
+      contact@openairinterface.org
+
+-->
+<testCaseList>
+        <htmlTabRef>rfsim-5gnr-down-f1-accelleran</htmlTabRef>
+        <htmlTabName>CleanUp</htmlTabName>
+        <htmlTabIcon>trash</htmlTabIcon>
+        <TestCaseRequestedList>
+ 100022
+ 222222
+        </TestCaseRequestedList>
+        <TestCaseExclusionList></TestCaseExclusionList>
+
+        <testCase id="100022">
+                <class>UndeployGenObject</class>
+                <desc>Undeploy all OAI 5G stack</desc>
+                <yaml_path>yaml_files/5g_rfsimulator_accelleran</yaml_path>
+        </testCase>
+
+        <testCase id="222222">
+                <class>Clean_Test_Server_Images</class>
+                <desc>Clean Test Images on Test Server</desc>
+                <test_svr_id>0</test_svr_id>
+        </testCase>
+
+</testCaseList>
diff --git a/ci-scripts/yaml_files/5g_rfsimulator_accelleran/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_accelleran/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3551cc27c62b90fb42fbc5a48106c0e4beb9ad06
--- /dev/null
+++ b/ci-scripts/yaml_files/5g_rfsimulator_accelleran/docker-compose.yaml
@@ -0,0 +1,38 @@
+version: '3.8'
+services:
+    oai-du:
+        image: oaisoftwarealliance/oai-gnb:develop
+        privileged: true
+        container_name: rfsim5g-oai-du
+        network_mode: "host"
+        environment:
+            RFSIMULATOR: server
+            USE_VOLUMED_CONF: 'yes'
+            USE_ADDITIONAL_OPTIONS: --sa --rfsim --MACRLCs.[0].local_n_address 172.21.16.109 --MACRLCs.[0].remote_n_address 172.21.6.22 --log_config.global_log_options level,nocolor,time
+        volumes:
+            - ../../conf_files/gnb-du.band78.106prb.rfsim.conf:/opt/oai-gnb/etc/mounted.conf
+        healthcheck:
+            test: /bin/bash -c "pgrep nr-softmodem"
+            interval: 10s
+            timeout: 5s
+            retries: 5
+    oai-nr-ue:
+        image: oaisoftwarealliance/oai-nr-ue:develop
+        privileged: true
+        container_name: rfsim5g-oai-nr-ue
+        network_mode: "host"
+        environment:
+            RFSIMULATOR: 127.0.0.1
+            FULL_IMSI: '208990100001140'
+            FULL_KEY: 'fec86ba6eb707ed08905757b1bb44b8f'
+            OPC: 'C42449363BBAD02B66D16BC975D77CC1'
+            DNN: oai
+            NSSAI_SST: 1
+            USE_ADDITIONAL_OPTIONS: --sa --rfsim -r 106 --numerology 1 -C 3619200000 --log_config.global_log_options level,nocolor,time
+        depends_on:
+            - oai-du
+        healthcheck:
+            test: /bin/bash -c "pgrep nr-uesoftmodem"
+            interval: 10s
+            timeout: 5s
+            retries: 5
diff --git a/doc/F1-design.md b/doc/F1-design.md
index 80417b1ae3e73c20d52f590ecde03fd505fb2284..7e491f53461d37b2827243a3206bd72a004abba0 100644
--- a/doc/F1-design.md
+++ b/doc/F1-design.md
@@ -11,6 +11,7 @@
     </td>
   </tr>
 </table>
+
 # 1. Introduction
 
 We use OpenAirInterface source code, and it's regular deployment scheme.
@@ -208,3 +209,46 @@ DU rlc north output goes to du_pdcp_data_ind() that push a outgoing packet reque
 CU pdcp south output calls cu_rlc_data_ind() that do the same symetric processing.
 
 
+# High-level F1-C code structure
+
+The F1 interface is used internally between CU (mostly RRC) and DU (mostly MAC)
+to exchange information. In DL, the CU sends messages as defined by the
+callbacks in `mac_rrc_dl.h`, whose implementation is defined in files
+`mac_rrc_dl_direct.c` (monolithic) and `mac_rrc_dl_f1ap.c` (for F1AP). In the
+monolithic case, the RRC calls directly into the message handler on the DU side
+(`mac_rrc_dl_handler.c`). In the F1 case, an ITTI message is sent to the CU
+task, sending an ASN.1-encoded F1AP message. The DU side's DU task decodes the
+message, and then calls the corresponding handler in `mac_rrc_dl_handler.c`.
+Thus, the message flow is the same in both F1 and monolithic cases, with the
+difference that F1AP encodes the messages using ASN.1 and sends over a socket.
+
+In UL, the callbacks defined in `mac_rrc_ul.h` are implemented by
+`mac_rrc_ul_direct.c` (monolithic) and `mac_rrc_ul_f1ap.c` (F1). In the direct
+case, an ITTI message is directly sent to the RRC task (hence, there is no
+dedicated handler). In F1, the DU task receives the ITTI message, encodes using
+ASN.1, and sends it over a network socket.  The CU task decodes, and sends the
+same ITTI message to the RRC task as done directly in the monolithic case.
+
+```
+                             +-------------+
+                             |             |
+                             |   CU/RRC    |
+                             |             |
+                             +-------------+
+                                |       ^
+     Callback def: mac_rrc_dl.h |       | No handler needed:
+     F1 impl: mac_rrc_dl_f1ap.c |       | RRC has ITTI
+Monolithic: mac_rrc_dl_direct.c |       |
+                                |       |
+                             DL |       | UL
+                                |       |
+                                |       | Callback def: mac_rrc_ul.h
+               Message handler: |       | F1 impl: mac_rrc_ul_f1ap.c
+           mac_rrc_dl_handler.c |       | Monolithic: mac_rrc_ul_direct.c
+                                v       |
+                             +-------------+
+                             |             |
+                             |   DU/MAC    |
+                             |             |
+                             +-------------+
+```
diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md
index aef3a7f63e7e63934cc279645e21d56da866d6de..3f50abaa115ff5c89bb469326f5c2dc4d47bacfe 100644
--- a/doc/TESTBenches.md
+++ b/doc/TESTBenches.md
@@ -82,6 +82,9 @@ Webhook
 - [RAN-gNB-N300-Timing-Phytest-LDPC](https://jenkins-oai.eurecom.fr/view/RAN/job/RAN-gNB-N300-Timing-Phytest-LDPC/)
   - caracal + N310
   - pure performance test through phy-test scheduler, see command line for more details
+- [RAN-Interop-F1](https://jenkins-oai.eurecom.fr/job/RAN-Interop-F1/)
+  - ofqot (DU, 1x UE)
+  - F1 interoperability testing: sets up connection to Accelleran CU, UE connection and connectivity test
 - [RAN-L2-Sim-Test-5G](https://jenkins-oai.eurecom.fr/job/RAN-L2-Sim-Test-4G/)
   - obelix (eNB, 1x UE, OAI EPC)
   - L2simulator: skips physical layer and uses proxy between eNB and UE
diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c
index a7465ad99b7cbff6a9678dc29cfb73ef3536c1cf..0007a3b4d109dd6b3e8ad97863e046d67f306049 100644
--- a/openair1/SIMULATION/NR_PHY/dlsim.c
+++ b/openair1/SIMULATION/NR_PHY/dlsim.c
@@ -1010,7 +1010,10 @@ int main(int argc, char **argv)
         UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1);
         UE_info->UE_sched_ctrl.harq_processes[harq_pid].round = round;
 
+        // nr_schedule_ue_spec() requires the mutex to be locked
+        NR_SCHED_LOCK(&gNB_mac->sched_lock);
         nr_schedule_ue_spec(0, frame, slot, &Sched_INFO->DL_req, &Sched_INFO->TX_req);
+        NR_SCHED_UNLOCK(&gNB_mac->sched_lock);
         Sched_INFO->module_id = 0;
         Sched_INFO->CC_id = 0;
         Sched_INFO->frame = frame;
diff --git a/openair2/F1AP/f1ap_du_ue_context_management.c b/openair2/F1AP/f1ap_du_ue_context_management.c
index fbe838871415710e1fcd8b26950111e837292d37..4c119dd8ad3787d4703df1070a5b0cb7649ac788 100644
--- a/openair2/F1AP/f1ap_du_ue_context_management.c
+++ b/openair2/F1AP/f1ap_du_ue_context_management.c
@@ -35,6 +35,7 @@
 #include "f1ap_decoder.h"
 #include "f1ap_itti_messaging.h"
 #include "f1ap_du_ue_context_management.h"
+#include "openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h"
 
 #include "rrc_extern.h"
 #include "openair2/RRC/NR/rrc_gNB_UE_context.h"
@@ -65,13 +66,13 @@ bool lteDURecvCb(protocol_ctxt_t  *ctxt_pP,
 int DU_handle_UE_CONTEXT_SETUP_REQUEST(instance_t       instance,
                                        uint32_t         assoc_id,
                                        uint32_t         stream,
-                                       F1AP_F1AP_PDU_t *pdu) {
-  MessageDef                      *msg_p; // message to RRC
+                                       F1AP_F1AP_PDU_t *pdu)
+{
   F1AP_UEContextSetupRequest_t    *container;
   int i;
   DevAssert(pdu);
-  msg_p = itti_alloc_new_message(TASK_DU_F1, 0,  F1AP_UE_CONTEXT_SETUP_REQ);
-  f1ap_ue_context_setup_t *f1ap_ue_context_setup_req = &F1AP_UE_CONTEXT_SETUP_REQ(msg_p);
+  f1ap_ue_context_setup_t ue_context_setup = {0};
+  f1ap_ue_context_setup_t *f1ap_ue_context_setup_req = &ue_context_setup;
   container = &pdu->choice.initiatingMessage->value.choice.UEContextSetupRequest;
   /* GNB_CU_UE_F1AP_ID */
   F1AP_UEContextSetupRequestIEs_t *ieCU;
@@ -224,8 +225,7 @@ int DU_handle_UE_CONTEXT_SETUP_REQUEST(instance_t       instance,
     LOG_W(F1AP, "can't find RRCContainer in UEContextSetupRequestIEs by id %ld \n", F1AP_ProtocolIE_ID_id_RRCContainer);
   }
 
-  itti_send_msg_to_task(TASK_RRC_GNB, instance, msg_p);
-
+  ue_context_setup_request(f1ap_ue_context_setup_req);
   return 0;
 }
 
diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c
index 1a72fa951d39bcb809149fed01b15fdfc88cddb1..4ba189c4bdd10a532fc0631f68fa8fbfd6927423 100644
--- a/openair2/LAYER2/NR_MAC_gNB/config.c
+++ b/openair2/LAYER2/NR_MAC_gNB/config.c
@@ -99,73 +99,52 @@ static void process_rlcBearerConfig(struct NR_CellGroupConfig__rlc_BearerToAddMo
 
 static void process_drx_Config(NR_UE_sched_ctrl_t *sched_ctrl, NR_SetupRelease_DRX_Config_t *drx_Config)
 {
-
- if (!drx_Config) return;
- AssertFatal(drx_Config->present != NR_SetupRelease_DRX_Config_PR_NOTHING, "Cannot have NR_SetupRelease_DRX_Config_PR_NOTHING\n");
-
- if (drx_Config->present == NR_SetupRelease_DRX_Config_PR_setup) {
-   LOG_I(NR_MAC,"Adding DRX config\n");
- }
- else {
-   LOG_I(NR_MAC,"Removing DRX config\n");
- }
+  AssertFatal(false, "%s() not implemented\n", __func__);
+  AssertFatal(drx_Config->present != NR_SetupRelease_DRX_Config_PR_NOTHING, "Cannot have NR_SetupRelease_DRX_Config_PR_NOTHING\n");
 }
 
 static void process_schedulingRequestConfig(NR_UE_sched_ctrl_t *sched_ctrl, NR_SchedulingRequestConfig_t *schedulingRequestConfig)
 {
- if (!schedulingRequestConfig) return;
-
-   LOG_I(NR_MAC,"Adding SchedulingRequestconfig\n");
+  AssertFatal(false, "%s() not implemented\n", __func__);
 }
 
 static void process_bsrConfig(NR_UE_sched_ctrl_t *sched_ctrl, NR_BSR_Config_t *bsr_Config)
 {
-  if (!bsr_Config) return;
-  LOG_I(NR_MAC,"Adding BSR config\n");
+  AssertFatal(false, "%s() not implemented\n", __func__);
 }
 
 static void process_tag_Config(NR_UE_sched_ctrl_t *sched_ctrl, NR_TAG_Config_t *tag_Config)
 {
-  if (!tag_Config) return;
-  LOG_I(NR_MAC,"Adding TAG config\n");
+  AssertFatal(false, "%s() not implemented\n", __func__);
 }
 
 static void process_phr_Config(NR_UE_sched_ctrl_t *sched_ctrl, NR_SetupRelease_PHR_Config_t *phr_Config)
 {
-   if (!phr_Config) return;
-   AssertFatal(phr_Config->present != NR_SetupRelease_PHR_Config_PR_NOTHING, "Cannot have NR_SetupRelease_PHR_Config_PR_NOTHING\n");
-
-   if (phr_Config->present == NR_SetupRelease_PHR_Config_PR_setup) {
-     LOG_I(NR_MAC,"Adding PHR config\n");
-   }
-   else {
-     LOG_I(NR_MAC,"Removing PHR config\n");
-   }
+  AssertFatal(false, "%s() not implemented\n", __func__);
 }
 
 void process_CellGroup(NR_CellGroupConfig_t *CellGroup, NR_UE_sched_ctrl_t *sched_ctrl)
 {
+  /* we assume that this function is mutex-protected from outside */
+  NR_SCHED_ENSURE_LOCKED(&RC.nrmac[0]->sched_lock);
+
    AssertFatal(CellGroup, "CellGroup is null\n");
    NR_MAC_CellGroupConfig_t *mac_CellGroupConfig = CellGroup->mac_CellGroupConfig;
 
    if (mac_CellGroupConfig) {
-     process_drx_Config(sched_ctrl,mac_CellGroupConfig->drx_Config);
-     process_schedulingRequestConfig(sched_ctrl,mac_CellGroupConfig->schedulingRequestConfig);
-     process_bsrConfig(sched_ctrl,mac_CellGroupConfig->bsr_Config);
-     process_tag_Config(sched_ctrl,mac_CellGroupConfig->tag_Config);
-     process_phr_Config(sched_ctrl,mac_CellGroupConfig->phr_Config);
-   }
-   else {
-     // apply defaults
-
+     //process_drx_Config(sched_ctrl,mac_CellGroupConfig->drx_Config);
+     //process_schedulingRequestConfig(sched_ctrl,mac_CellGroupConfig->schedulingRequestConfig);
+     //process_bsrConfig(sched_ctrl,mac_CellGroupConfig->bsr_Config);
+     //process_tag_Config(sched_ctrl,mac_CellGroupConfig->tag_Config);
+     //process_phr_Config(sched_ctrl,mac_CellGroupConfig->phr_Config);
    }
 
    process_rlcBearerConfig(CellGroup->rlc_BearerToAddModList,CellGroup->rlc_BearerToReleaseList,sched_ctrl);
 
 }
 
-void config_common(gNB_MAC_INST *nrmac, int pdsch_AntennaPorts, int pusch_AntennaPorts, NR_ServingCellConfigCommon_t *scc) {
-
+static void config_common(gNB_MAC_INST *nrmac, int pdsch_AntennaPorts, int pusch_AntennaPorts, NR_ServingCellConfigCommon_t *scc)
+{
   nfapi_nr_config_request_scf_t *cfg = &nrmac->config[0];
   nrmac->common_channels[0].ServingCellConfigCommon = scc;
 
@@ -460,19 +439,23 @@ void config_common(gNB_MAC_INST *nrmac, int pdsch_AntennaPorts, int pusch_Antenn
   }
 }
 
-int nr_mac_enable_ue_rrc_processing_timer(module_id_t Mod_idP, rnti_t rnti, NR_SubcarrierSpacing_t subcarrierSpacing, uint32_t rrc_reconfiguration_delay) {
-
+int nr_mac_enable_ue_rrc_processing_timer(module_id_t Mod_idP, rnti_t rnti, NR_SubcarrierSpacing_t subcarrierSpacing, uint32_t rrc_reconfiguration_delay)
+{
   if (rrc_reconfiguration_delay == 0) {
     return -1;
   }
 
-  NR_UE_info_t *UE_info = find_nr_UE(&RC.nrmac[Mod_idP]->UE_info,rnti);
+  gNB_MAC_INST *nrmac = RC.nrmac[Mod_idP];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
+
+  NR_UE_info_t *UE_info = find_nr_UE(&nrmac->UE_info,rnti);
   if (!UE_info) {
     LOG_W(NR_MAC, "Could not find UE for RNTI 0x%04x\n", rnti);
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     return -1;
   }
   NR_UE_sched_ctrl_t *sched_ctrl = &UE_info->UE_sched_ctrl;
-  const uint16_t sl_ahead = RC.nrmac[Mod_idP]->if_inst->sl_ahead;
+  const uint16_t sl_ahead = nrmac->if_inst->sl_ahead;
   sched_ctrl->rrc_processing_timer = (rrc_reconfiguration_delay<<subcarrierSpacing) + sl_ahead;
   LOG_I(NR_MAC, "Activating RRC processing timer for UE %04x with %d ms\n", UE_info->rnti, rrc_reconfiguration_delay);
 
@@ -480,8 +463,9 @@ int nr_mac_enable_ue_rrc_processing_timer(module_id_t Mod_idP, rnti_t rnti, NR_S
   // processing timer. To prevent this, set a variable as if we would have just
   // sent it. This way, another TA command will for sure be sent in some
   // frames, after RRC processing timer.
-  sched_ctrl->ta_frame = (RC.nrmac[Mod_idP]->frame - 1 + 1024) % 1024;
+  sched_ctrl->ta_frame = (nrmac->frame - 1 + 1024) % 1024;
 
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
   return 0;
 }
 
@@ -494,6 +478,7 @@ void nr_mac_config_scc(gNB_MAC_INST *nrmac,
 {
   DevAssert(nrmac != NULL);
   AssertFatal(nrmac->common_channels[0].ServingCellConfigCommon == NULL, "logic error: multiple configurations of SCC\n");
+  NR_SCHED_LOCK(&nrmac->sched_lock);
 
   DevAssert(scc != NULL);
   AssertFatal(scc->ssb_PositionsInBurst->present > 0 && scc->ssb_PositionsInBurst->present < 4,
@@ -573,26 +558,31 @@ void nr_mac_config_scc(gNB_MAC_INST *nrmac,
         ra->preambles.preamble_list[i] = i;
     }
   }
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 void nr_mac_config_mib(gNB_MAC_INST *nrmac, NR_BCCH_BCH_Message_t *mib)
 {
   DevAssert(nrmac != NULL);
   DevAssert(mib != NULL);
+  NR_SCHED_LOCK(&nrmac->sched_lock);
   NR_COMMON_channels_t *cc = &nrmac->common_channels[0];
 
   AssertFatal(cc->mib == NULL, "logic bug: updated MIB multiple times\n");
   cc->mib = mib;
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 void nr_mac_config_sib1(gNB_MAC_INST *nrmac, NR_BCCH_DL_SCH_Message_t *sib1)
 {
   DevAssert(nrmac != NULL);
   DevAssert(sib1 != NULL);
+  NR_SCHED_LOCK(&nrmac->sched_lock);
   NR_COMMON_channels_t *cc = &nrmac->common_channels[0];
 
   AssertFatal(cc->sib1 == NULL, "logic bug: updated SIB1 multiple times\n");
   cc->sib1 = sib1;
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup)
@@ -600,16 +590,17 @@ bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t
   DevAssert(nrmac != NULL);
   DevAssert(CellGroup != NULL);
   DevAssert(get_softmodem_params()->phy_test);
+  NR_SCHED_LOCK(&nrmac->sched_lock);
 
   NR_UE_info_t* UE = add_new_nr_ue(nrmac, rnti, CellGroup);
   if (UE) {
     LOG_I(NR_MAC,"Force-added new UE %x with initial CellGroup\n", rnti);
+    process_CellGroup(CellGroup,&UE->UE_sched_ctrl);
   } else {
     LOG_E(NR_MAC,"Error adding UE %04x\n", rnti);
-    return false;
   }
-  process_CellGroup(CellGroup,&UE->UE_sched_ctrl);
-  return true;
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
+  return UE != NULL;
 }
 
 bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup)
@@ -617,6 +608,7 @@ bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupCo
   DevAssert(nrmac != NULL);
   DevAssert(CellGroup != NULL);
   DevAssert(!get_softmodem_params()->phy_test);
+  NR_SCHED_LOCK(&nrmac->sched_lock);
 
   // NSA case: need to pre-configure CFRA
   const int CC_id = 0;
@@ -628,6 +620,7 @@ bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupCo
   }
   if (ra_index == NR_NB_RA_PROC_MAX) {
     LOG_E(NR_MAC, "RA processes are not available for CFRA RNTI %04x\n", rnti);
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     return false;
   }
   NR_RA_t *ra = &cc->ra[ra_index];
@@ -656,12 +649,16 @@ bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupCo
     }
   }
   LOG_I(NR_MAC,"Added new RA process for UE RNTI %04x with initial CellGroup\n", rnti);
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
   return true;
 }
 
 bool nr_mac_update_cellgroup(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup)
 {
   DevAssert(nrmac != NULL);
+  /* we assume that this function is mutex-protected from outside */
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
+
   DevAssert(CellGroup != NULL);
 
   NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, rnti);
@@ -677,58 +674,7 @@ bool nr_mac_update_cellgroup(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupCon
     exit(1);
   }
 
-  nr_mac_update_RA(nrmac, rnti, CellGroup);
   process_CellGroup(CellGroup, &UE->UE_sched_ctrl);
 
   return true;
 }
-
-bool nr_mac_update_RA(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup)
-{
-  // Checking for free RA process
-  NR_COMMON_channels_t *cc = &nrmac->common_channels[0];
-  uint8_t ra_index = 0;
-  for (; ra_index < NR_NB_RA_PROC_MAX; ra_index++) {
-    if ((cc->ra[ra_index].state == RA_IDLE) && (!cc->ra[ra_index].cfra))
-      break;
-  }
-  if (ra_index == NR_NB_RA_PROC_MAX) {
-    LOG_E(NR_MAC, "%s() %s:%d RA processes are not available for CFRA RNTI :%x\n", __FUNCTION__, __FILE__, __LINE__, rnti);
-    return -1;
-  }
-
-  NR_RA_t *ra = &cc->ra[ra_index];
-  if (CellGroup->spCellConfig && CellGroup->spCellConfig->reconfigurationWithSync
-      && CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated != NULL) {
-    if (CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra != NULL) {
-      ra->cfra = true;
-      ra->rnti = rnti;
-      ra->CellGroup = CellGroup;
-      struct NR_CFRA *cfra = CellGroup->spCellConfig->reconfigurationWithSync->rach_ConfigDedicated->choice.uplink->cfra;
-      uint8_t num_preamble = cfra->resources.choice.ssb->ssb_ResourceList.list.count;
-      ra->preambles.num_preambles = num_preamble;
-      ra->preambles.preamble_list = (uint8_t *)malloc(num_preamble * sizeof(uint8_t));
-      for (int i = 0; i < cc->num_active_ssb; i++) {
-        for (int j = 0; j < num_preamble; j++) {
-          if (cc->ssb_index[i] == cfra->resources.choice.ssb->ssb_ResourceList.list.array[j]->ssb) {
-            // One dedicated preamble for each beam
-            ra->preambles.preamble_list[i] = cfra->resources.choice.ssb->ssb_ResourceList.list.array[j]->ra_PreambleIndex;
-            break;
-          }
-        }
-      }
-    }
-  } else {
-    ra->cfra = false;
-    ra->rnti = 0;
-    if (ra->preambles.preamble_list == NULL) {
-      ra->preambles.num_preambles = MAX_NUM_NR_PRACH_PREAMBLES;
-      ra->preambles.preamble_list = (uint8_t *)malloc(MAX_NUM_NR_PRACH_PREAMBLES * sizeof(uint8_t));
-      for (int i = 0; i < MAX_NUM_NR_PRACH_PREAMBLES; i++)
-        ra->preambles.preamble_list[i] = i;
-    }
-  }
-  LOG_I(NR_MAC, "Added new %s process for UE RNTI %04x with initial CellGroup\n", ra->cfra ? "CFRA" : "CBRA", rnti);
-
-  return true;
-}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c
index e831f6d4a86b4e687e208a97865d65a287358914..2d4cd16eb4a9998e9513969de1049e8adf8ca09b 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c
@@ -63,6 +63,7 @@ void clear_nr_nfapi_information(gNB_MAC_INST *gNB,
                                 nfapi_nr_tx_data_request_t *TX_req,
                                 nfapi_nr_ul_dci_request_t *UL_dci_req)
 {
+  /* called below and in simulators, so we assume a lock but don't require it */
 
   NR_ServingCellConfigCommon_t *scc = gNB->common_channels->ServingCellConfigCommon;
   const int num_slots = nr_slots_per_frame[*scc->ssbSubcarrierSpacing];
@@ -149,6 +150,8 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, sub_frame_
   NR_COMMON_channels_t *cc = gNB->common_channels;
   NR_ServingCellConfigCommon_t        *scc     = cc->ServingCellConfigCommon;
 
+  NR_SCHED_LOCK(&gNB->sched_lock);
+
   if (slot==0 && (*scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0]>=257)) {
     //FR2
     const NR_TDD_UL_DL_Pattern_t *tdd = &scc->tdd_UL_DL_ConfigurationCommon->pattern1;
@@ -254,6 +257,6 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, sub_frame_
   copy_ul_tti_req(&sched_info->UL_tti_req, &gNB->UL_tti_req_ahead[0][current_index]);
 
   stop_meas(&gNB->eNB_scheduler);
-
+  NR_SCHED_UNLOCK(&gNB->sched_lock);
   VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_OUT);
 }
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c
index 2461d9618d988b4d8e3a7e04c8fc0e9dfcb743be..fb78ff0d1339c2193b7cea426730af864be6dd22 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c
@@ -49,17 +49,32 @@ extern RAN_CONTEXT_t RC;
 extern const uint8_t nr_slots_per_frame[5];
 extern uint16_t sl_ahead;
 
+// forward declaration of functions used in this file
+static void fill_msg3_pusch_pdu(nfapi_nr_pusch_pdu_t *pusch_pdu,
+                                NR_ServingCellConfigCommon_t *scc,
+                                int round,
+                                int startSymbolAndLength,
+                                rnti_t rnti,
+                                int scs,
+                                int bwp_size,
+                                int bwp_start,
+                                int mappingtype,
+                                int fh,
+                                int msg3_first_rb,
+                                int msg3_nb_rb);
+static void nr_fill_rar(uint8_t Mod_idP, NR_RA_t *ra, uint8_t *dlsch_buffer, nfapi_nr_pusch_pdu_t *pusch_pdu);
+
 static const uint8_t DELTA[4] = {2, 3, 4, 6};
 
 static const float ssb_per_rach_occasion[8] = {0.125, 0.25, 0.5, 1, 2, 4, 8};
 
-int16_t ssb_index_from_prach(module_id_t module_idP,
-                             frame_t frameP,
-			     sub_frame_t slotP,
-			     uint16_t preamble_index,
-			     uint8_t freq_index,
-			     uint8_t symbol) {
-  
+static int16_t ssb_index_from_prach(module_id_t module_idP,
+                                    frame_t frameP,
+                                    sub_frame_t slotP,
+                                    uint16_t preamble_index,
+                                    uint8_t freq_index,
+                                    uint8_t symbol)
+{
   gNB_MAC_INST *gNB = RC.nrmac[module_idP];
   NR_COMMON_channels_t *cc = &gNB->common_channels[0];
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
@@ -139,10 +154,12 @@ int16_t ssb_index_from_prach(module_id_t module_idP,
   return index;
 }
 
-
 //Compute Total active SSBs and RO available
 void find_SSB_and_RO_available(gNB_MAC_INST *nrmac)
 {
+  /* already mutex protected through nr_mac_config_scc() */
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
+
   NR_COMMON_channels_t *cc = &nrmac->common_channels[0];
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
   nfapi_nr_config_request_scf_t *cfg = &nrmac->config[0];
@@ -242,6 +259,9 @@ void find_SSB_and_RO_available(gNB_MAC_INST *nrmac)
 void schedule_nr_prach(module_id_t module_idP, frame_t frameP, sub_frame_t slotP)
 {
   gNB_MAC_INST *gNB = RC.nrmac[module_idP];
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&gNB->sched_lock);
+
   NR_COMMON_channels_t *cc = gNB->common_channels;
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
   int mu;
@@ -399,15 +419,20 @@ void schedule_nr_prach(module_id_t module_idP, frame_t frameP, sub_frame_t slotP
   }
 }
 
-void nr_schedule_msg2(uint16_t rach_frame, uint16_t rach_slot,
-                      uint16_t *msg2_frame, uint16_t *msg2_slot,
-                      int mu, NR_ServingCellConfigCommon_t *scc,
-                      frame_type_t frame_type,
-                      uint16_t monitoring_slot_period,
-                      uint16_t monitoring_offset,uint8_t beam_index,
-                      uint8_t num_active_ssb,
-                      int16_t *tdd_beam_association, int sl_ahead){
-
+static void nr_schedule_msg2(uint16_t rach_frame,
+                             uint16_t rach_slot,
+                             uint16_t *msg2_frame,
+                             uint16_t *msg2_slot,
+                             int mu,
+                             NR_ServingCellConfigCommon_t *scc,
+                             frame_type_t frame_type,
+                             uint16_t monitoring_slot_period,
+                             uint16_t monitoring_offset,
+                             uint8_t beam_index,
+                             uint8_t num_active_ssb,
+                             int16_t *tdd_beam_association,
+                             int sl_ahead)
+{
   // preferentially we schedule the msg2 in the mixed slot or in the last dl slot
   // if they are allowed by search space configuration
   uint8_t response_window = scc->uplinkConfigCommon->initialUplinkBWP->rach_ConfigCommon->choice.setup->rach_ConfigGeneric.ra_ResponseWindow;
@@ -516,12 +541,13 @@ void nr_initiate_ra_proc(module_id_t module_idP,
                          uint16_t preamble_index,
                          uint8_t freq_index,
                          uint8_t symbol,
-                         int16_t timing_offset){
+                         int16_t timing_offset)
+{
+  gNB_MAC_INST *nr_mac = RC.nrmac[module_idP];
+  NR_SCHED_LOCK(&nr_mac->sched_lock);
 
   uint8_t ul_carrier_id = 0; // 0 for NUL 1 for SUL
-
   uint16_t msg2_frame, msg2_slot,monitoring_slot_period,monitoring_offset;
-  gNB_MAC_INST *nr_mac = RC.nrmac[module_idP];
   NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id];
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
   frame_type_t frame_type = cc->frame_type;
@@ -655,57 +681,22 @@ void nr_initiate_ra_proc(module_id_t module_idP,
           cc->ssb_index[beam_index],
           i);
 
+    NR_SCHED_UNLOCK(&nr_mac->sched_lock);
     return;
   }
+
+  NR_SCHED_UNLOCK(&nr_mac->sched_lock);
   LOG_E(NR_MAC, "[gNB %d][RAPROC] FAILURE: CC_id %d Frame %d initiating RA procedure for preamble index %d\n", module_idP, CC_id, frameP, preamble_index);
 
   VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_INITIATE_RA_PROC, 0);
 }
 
-void nr_schedule_RA(module_id_t module_idP,
-                    frame_t frameP,
-                    sub_frame_t slotP,
-                    nfapi_nr_ul_dci_request_t *ul_dci_req,
-                    nfapi_nr_dl_tti_request_t *DL_req,
-                    nfapi_nr_tx_data_request_t *TX_req)
-{
-  gNB_MAC_INST *mac = RC.nrmac[module_idP];
-
-  start_meas(&mac->schedule_ra);
-  for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
-    NR_COMMON_channels_t *cc = &mac->common_channels[CC_id];
-    for (int i = 0; i < NR_NB_RA_PROC_MAX; i++) {
-      NR_RA_t *ra = &cc->ra[i];
-      LOG_D(NR_MAC, "RA[state:%d]\n", ra->state);
-      switch (ra->state) {
-        case Msg2:
-          nr_generate_Msg2(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req);
-          break;
-        case Msg3_retransmission:
-          nr_generate_Msg3_retransmission(module_idP, CC_id, frameP, slotP, ra, ul_dci_req);
-          break;
-        case Msg3_dcch_dtch:
-          nr_generate_Msg3_dcch_dtch_response(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req);
-        case Msg4:
-          nr_generate_Msg4(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req);
-          break;
-        case WAIT_Msg4_ACK:
-          nr_check_Msg4_Ack(module_idP, CC_id, frameP, slotP, ra);
-          break;
-        default:
-          break;
-      }
-    }
-  }
-  stop_meas(&mac->schedule_ra);
-}
-
-void nr_generate_Msg3_retransmission(module_id_t module_idP,
-                                     int CC_id,
-                                     frame_t frame,
-                                     sub_frame_t slot,
-                                     NR_RA_t *ra,
-                                     nfapi_nr_ul_dci_request_t *ul_dci_req)
+static void nr_generate_Msg3_retransmission(module_id_t module_idP,
+                                            int CC_id,
+                                            frame_t frame,
+                                            sub_frame_t slot,
+                                            NR_RA_t *ra,
+                                            nfapi_nr_ul_dci_request_t *ul_dci_req)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_idP];
   NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id];
@@ -877,15 +868,14 @@ void nr_generate_Msg3_retransmission(module_id_t module_idP,
   }
 }
 
-void nr_get_Msg3alloc(module_id_t module_id,
-                      int CC_id,
-                      NR_ServingCellConfigCommon_t *scc,
-                      sub_frame_t current_slot,
-                      frame_t current_frame,
-                      NR_RA_t *ra,
-                      int16_t *tdd_beam_association)
+static void nr_get_Msg3alloc(module_id_t module_id,
+                             int CC_id,
+                             NR_ServingCellConfigCommon_t *scc,
+                             sub_frame_t current_slot,
+                             frame_t current_frame,
+                             NR_RA_t *ra,
+                             int16_t *tdd_beam_association)
 {
-
   // msg3 is scheduled in mixed slot in the following TDD period
 
   uint16_t msg3_nb_rb = 8; // sdu has 6 or 8 bytes
@@ -999,16 +989,19 @@ void nr_get_Msg3alloc(module_id_t module_id,
   ra->msg3_bwp_start = bwpStart;
 }
 
-
-void fill_msg3_pusch_pdu(nfapi_nr_pusch_pdu_t *pusch_pdu,
-                         NR_ServingCellConfigCommon_t *scc,
-                         int round,
-                         int startSymbolAndLength,
-                         rnti_t rnti, int scs,
-                         int bwp_size, int bwp_start,
-                         int mappingtype, int fh,
-                         int msg3_first_rb, int msg3_nb_rb) {
-
+static void fill_msg3_pusch_pdu(nfapi_nr_pusch_pdu_t *pusch_pdu,
+                                NR_ServingCellConfigCommon_t *scc,
+                                int round,
+                                int startSymbolAndLength,
+                                rnti_t rnti,
+                                int scs,
+                                int bwp_size,
+                                int bwp_start,
+                                int mappingtype,
+                                int fh,
+                                int msg3_first_rb,
+                                int msg3_nb_rb)
+{
   int start_symbol_index,nr_of_symbols;
 
   SLIV2SL(startSymbolAndLength, &start_symbol_index, &nr_of_symbols);
@@ -1087,7 +1080,7 @@ void fill_msg3_pusch_pdu(nfapi_nr_pusch_pdu_t *pusch_pdu,
   }
 }
 
-void nr_add_msg3(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra, uint8_t *RAR_pdu)
+static void nr_add_msg3(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t slotP, NR_RA_t *ra, uint8_t *RAR_pdu)
 {
   gNB_MAC_INST *mac = RC.nrmac[module_idP];
   NR_COMMON_channels_t *cc = &mac->common_channels[CC_id];
@@ -1155,13 +1148,14 @@ void nr_add_msg3(module_id_t module_idP, int CC_id, frame_t frameP, sub_frame_t
   nr_fill_rar(module_idP, ra, RAR_pdu, pusch_pdu);
 }
 
-void nr_generate_Msg2(module_id_t module_idP,
-                      int CC_id,
-                      frame_t frameP,
-                      sub_frame_t slotP,
-                      NR_RA_t *ra,
-                      nfapi_nr_dl_tti_request_t *DL_req,
-                      nfapi_nr_tx_data_request_t *TX_req)
+
+static void nr_generate_Msg2(module_id_t module_idP,
+                             int CC_id,
+                             frame_t frameP,
+                             sub_frame_t slotP,
+                             NR_RA_t *ra,
+                             nfapi_nr_dl_tti_request_t *DL_req,
+                             nfapi_nr_tx_data_request_t *TX_req)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_idP];
   NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id];
@@ -1427,22 +1421,29 @@ void nr_generate_Msg2(module_id_t module_idP,
   }
 }
 
-void prepare_dl_pdus(gNB_MAC_INST *nr_mac,
-                     NR_RA_t *ra,
-                     NR_UE_DL_BWP_t *dl_bwp,
-                     nfapi_nr_dl_tti_request_body_t *dl_req,
-                     NR_sched_pucch_t *pucch,
-                     NR_pdsch_dmrs_t dmrs_info,
-                     NR_tda_info_t tda,
-                     int aggregation_level,
-                     int CCEIndex,
-                     int tb_size, int ndi,
-                     int tpc, int delta_PRI,
-                     int current_harq_pid,
-                     int time_domain_assignment,
-                     int CC_id, int rnti, int round,
-                     int mcsIndex, int tb_scaling,
-                     int pduindex, int rbStart, int rbSize)
+static void prepare_dl_pdus(gNB_MAC_INST *nr_mac,
+                            NR_RA_t *ra,
+                            NR_UE_DL_BWP_t *dl_bwp,
+                            nfapi_nr_dl_tti_request_body_t *dl_req,
+                            NR_sched_pucch_t *pucch,
+                            NR_pdsch_dmrs_t dmrs_info,
+                            NR_tda_info_t tda,
+                            int aggregation_level,
+                            int CCEIndex,
+                            int tb_size,
+                            int ndi,
+                            int tpc,
+                            int delta_PRI,
+                            int current_harq_pid,
+                            int time_domain_assignment,
+                            int CC_id,
+                            int rnti,
+                            int round,
+                            int mcsIndex,
+                            int tb_scaling,
+                            int pduindex,
+                            int rbStart,
+                            int rbSize)
 {
   // look up the PDCCH PDU for this CC, BWP, and CORESET. If it does not exist, create it. This is especially
   // important if we have multiple RAs, and the DLSCH has to reuse them, so we need to mark them
@@ -1613,13 +1614,13 @@ void prepare_dl_pdus(gNB_MAC_INST *nr_mac,
     LOG_D(NR_MAC,"numDlDci: %i\n", pdcch_pdu_rel15->numDlDci);
 }
 
-void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP,
-                                         int CC_id,
-                                         frame_t frameP,
-                                         sub_frame_t slotP,
-                                         NR_RA_t *ra,
-                                         nfapi_nr_dl_tti_request_t *DL_req,
-                                         nfapi_nr_tx_data_request_t *TX_req)
+static void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP,
+                                                int CC_id,
+                                                frame_t frameP,
+                                                sub_frame_t slotP,
+                                                NR_RA_t *ra,
+                                                nfapi_nr_dl_tti_request_t *DL_req,
+                                                nfapi_nr_tx_data_request_t *TX_req)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_idP];
 
@@ -1799,13 +1800,13 @@ void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP,
   sched_ctrl->ul_failure = 0;
 }
 
-void nr_generate_Msg4(module_id_t module_idP,
-                      int CC_id,
-                      frame_t frameP,
-                      sub_frame_t slotP,
-                      NR_RA_t *ra,
-                      nfapi_nr_dl_tti_request_t *DL_req,
-                      nfapi_nr_tx_data_request_t *TX_req)
+static void nr_generate_Msg4(module_id_t module_idP,
+                             int CC_id,
+                             frame_t frameP,
+                             sub_frame_t slotP,
+                             NR_RA_t *ra,
+                             nfapi_nr_dl_tti_request_t *DL_req,
+                             nfapi_nr_tx_data_request_t *TX_req)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_idP];
   NR_COMMON_channels_t *cc = &nr_mac->common_channels[CC_id];
@@ -2056,8 +2057,8 @@ void nr_generate_Msg4(module_id_t module_idP,
   }
 }
 
-void nr_check_Msg4_Ack(module_id_t module_id, int CC_id, frame_t frame, sub_frame_t slot, NR_RA_t *ra) {
-
+static void nr_check_Msg4_Ack(module_id_t module_id, int CC_id, frame_t frame, sub_frame_t slot, NR_RA_t *ra)
+{
   NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[module_id]->UE_info, ra->rnti);
   const int current_harq_pid = ra->harq_pid;
 
@@ -2098,7 +2099,10 @@ void nr_check_Msg4_Ack(module_id_t module_id, int CC_id, frame_t frame, sub_fram
   }
 }
 
-void nr_clear_ra_proc(module_id_t module_idP, int CC_id, frame_t frameP, NR_RA_t *ra){
+void nr_clear_ra_proc(module_id_t module_idP, int CC_id, frame_t frameP, NR_RA_t *ra)
+{
+  /* we assume that this function is mutex-protected from outside */
+  NR_SCHED_ENSURE_LOCKED(&RC.nrmac[module_idP]->sched_lock);
   LOG_D(NR_MAC,"[gNB %d][RAPROC] CC_id %d Frame %d Clear Random access information rnti %x\n", module_idP, CC_id, frameP, ra->rnti);
   ra->state = RA_IDLE;
   ra->timing_offset = 0;
@@ -2141,11 +2145,8 @@ void nr_clear_ra_proc(module_id_t module_idP, int CC_id, frame_t frameP, NR_RA_t
 // - sending only 1 RAR subPDU
 // - UL Grant: hardcoded CSI, TPC, time alloc
 // - padding
-void nr_fill_rar(uint8_t Mod_idP,
-                 NR_RA_t * ra,
-                 uint8_t * dlsch_buffer,
-                 nfapi_nr_pusch_pdu_t  *pusch_pdu){
-
+static void nr_fill_rar(uint8_t Mod_idP, NR_RA_t *ra, uint8_t *dlsch_buffer, nfapi_nr_pusch_pdu_t *pusch_pdu)
+{
   LOG_D(NR_MAC, "[gNB] Generate RAR MAC PDU frame %d slot %d preamble index %u TA command %d \n", ra->Msg2_frame, ra-> Msg2_slot, ra->preamble_index, ra->timing_offset);
   NR_RA_HEADER_BI *rarbi = (NR_RA_HEADER_BI *) dlsch_buffer;
   NR_RA_HEADER_RAPID *rarh = (NR_RA_HEADER_RAPID *) (dlsch_buffer + 1);
@@ -2239,3 +2240,43 @@ void nr_fill_rar(uint8_t Mod_idP,
         csi_req,
         t_crnti);
 }
+
+void nr_schedule_RA(module_id_t module_idP,
+                    frame_t frameP,
+                    sub_frame_t slotP,
+                    nfapi_nr_ul_dci_request_t *ul_dci_req,
+                    nfapi_nr_dl_tti_request_t *DL_req,
+                    nfapi_nr_tx_data_request_t *TX_req)
+{
+  gNB_MAC_INST *mac = RC.nrmac[module_idP];
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);
+
+  start_meas(&mac->schedule_ra);
+  for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
+    NR_COMMON_channels_t *cc = &mac->common_channels[CC_id];
+    for (int i = 0; i < NR_NB_RA_PROC_MAX; i++) {
+      NR_RA_t *ra = &cc->ra[i];
+      LOG_D(NR_MAC, "RA[state:%d]\n", ra->state);
+      switch (ra->state) {
+        case Msg2:
+          nr_generate_Msg2(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req);
+          break;
+        case Msg3_retransmission:
+          nr_generate_Msg3_retransmission(module_idP, CC_id, frameP, slotP, ra, ul_dci_req);
+          break;
+        case Msg3_dcch_dtch:
+          nr_generate_Msg3_dcch_dtch_response(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req);
+        case Msg4:
+          nr_generate_Msg4(module_idP, CC_id, frameP, slotP, ra, DL_req, TX_req);
+          break;
+        case WAIT_Msg4_ACK:
+          nr_check_Msg4_Ack(module_idP, CC_id, frameP, slotP, ra);
+          break;
+        default:
+          break;
+      }
+    }
+  }
+  stop_meas(&mac->schedule_ra);
+}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c
index f6d1f83e351d8e57c8279e2f64b43a7273589b8e..0d66ec0842e1bef67b8aea453ed00b326ef33769 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c
@@ -51,11 +51,15 @@
 
 extern RAN_CONTEXT_t RC;
 
-void schedule_ssb(frame_t frame, sub_frame_t slot,
-                  NR_ServingCellConfigCommon_t *scc,
-                  nfapi_nr_dl_tti_request_body_t *dl_req,
-                  int i_ssb, uint8_t scoffset, uint16_t offset_pointa, uint32_t payload) {
-
+static void schedule_ssb(frame_t frame,
+                         sub_frame_t slot,
+                         NR_ServingCellConfigCommon_t *scc,
+                         nfapi_nr_dl_tti_request_body_t *dl_req,
+                         int i_ssb,
+                         uint8_t scoffset,
+                         uint16_t offset_pointa,
+                         uint32_t payload)
+{
   uint8_t beam_index = 0;
   nfapi_nr_dl_tti_request_pdu_t  *dl_config_pdu = &dl_req->dl_tti_pdu_list[dl_req->nPDUs];
   memset((void *) dl_config_pdu, 0,sizeof(nfapi_nr_dl_tti_request_pdu_t));
@@ -86,12 +90,25 @@ void schedule_ssb(frame_t frame, sub_frame_t slot,
   dl_req->nPDUs++;
 
   LOG_D(MAC,"Scheduling ssb %d at frame %d and slot %d\n",i_ssb,frame,slot);
+}
 
+static void fill_ssb_vrb_map(NR_COMMON_channels_t *cc, int rbStart, int ssb_subcarrier_offset, uint16_t symStart, int CC_id)
+{
+  AssertFatal(*cc->ServingCellConfigCommon->ssbSubcarrierSpacing !=
+              NR_SubcarrierSpacing_kHz240,
+              "240kHZ subcarrier won't work with current VRB map because a single SSB might be across 2 slots\n");
+
+  uint16_t *vrb_map = cc[CC_id].vrb_map;
+
+  const int extra_prb = ssb_subcarrier_offset > 0;
+  for (int rb = 0; rb < 20+extra_prb; rb++)
+    vrb_map[rbStart + rb] = SL_to_bitmap(symStart, 4);
 }
 
 void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP, nfapi_nr_dl_tti_request_t *DL_req)
 {
   gNB_MAC_INST *gNB = RC.nrmac[module_idP];
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
   NR_COMMON_channels_t *cc;
   nfapi_nr_dl_tti_request_body_t *dl_req;
   NR_MIB_t *mib = RC.nrrrc[module_idP]->carrier.mib->message.choice.mib;
@@ -254,33 +271,15 @@ void schedule_nr_mib(module_id_t module_idP, frame_t frameP, sub_frame_t slotP,
   }
 }
 
-void schedule_nr_SI(module_id_t module_idP, frame_t frameP, sub_frame_t subframeP) {
-//----------------------------------------  
-}
-
-void fill_ssb_vrb_map (NR_COMMON_channels_t *cc, int rbStart, int ssb_subcarrier_offset, uint16_t symStart, int CC_id) {
-
-  AssertFatal(*cc->ServingCellConfigCommon->ssbSubcarrierSpacing !=
-              NR_SubcarrierSpacing_kHz240,
-              "240kHZ subcarrier won't work with current VRB map because a single SSB might be across 2 slots\n");
-
-  uint16_t *vrb_map = cc[CC_id].vrb_map;
-
-  const int extra_prb = ssb_subcarrier_offset > 0;
-  for (int rb = 0; rb < 20+extra_prb; rb++)
-    vrb_map[rbStart + rb] = SL_to_bitmap(symStart, 4);
-
-}
-
-uint32_t schedule_control_sib1(module_id_t module_id,
-                               int CC_id,
-                               NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config,
-                               int time_domain_allocation,
-                               NR_pdsch_dmrs_t *dmrs_parms,
-                               NR_tda_info_t *tda_info,
-                               uint8_t candidate_idx,
-                               uint16_t num_total_bytes) {
-
+static uint32_t schedule_control_sib1(module_id_t module_id,
+                                      int CC_id,
+                                      NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config,
+                                      int time_domain_allocation,
+                                      NR_pdsch_dmrs_t *dmrs_parms,
+                                      NR_tda_info_t *tda_info,
+                                      uint8_t candidate_idx,
+                                      uint16_t num_total_bytes)
+{
   gNB_MAC_INST *gNB_mac = RC.nrmac[module_id];
   NR_COMMON_channels_t *cc = &gNB_mac->common_channels[CC_id];
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
@@ -377,14 +376,14 @@ uint32_t schedule_control_sib1(module_id_t module_id,
   return TBS;
 }
 
-void nr_fill_nfapi_dl_sib1_pdu(int Mod_idP,
-                               nfapi_nr_dl_tti_request_body_t *dl_req,
-                               int pdu_index,
-                               NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config,
-                               uint32_t TBS,
-                               int StartSymbolIndex,
-                               int NrOfSymbols) {
-
+static void nr_fill_nfapi_dl_sib1_pdu(int Mod_idP,
+                                      nfapi_nr_dl_tti_request_body_t *dl_req,
+                                      int pdu_index,
+                                      NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config,
+                                      uint32_t TBS,
+                                      int StartSymbolIndex,
+                                      int NrOfSymbols)
+{
   gNB_MAC_INST *gNB_mac = RC.nrmac[Mod_idP];
   NR_COMMON_channels_t *cc = gNB_mac->common_channels;
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
@@ -515,7 +514,6 @@ void nr_fill_nfapi_dl_sib1_pdu(int Mod_idP,
   LOG_D(MAC,"ShiftIndex: %i\n", pdcch_pdu_rel15->ShiftIndex);
   LOG_D(MAC,"precoderGranularity: %i\n", pdcch_pdu_rel15->precoderGranularity);
   LOG_D(MAC,"numDlDci: %i\n", pdcch_pdu_rel15->numDlDci);
-
 }
 
 void schedule_nr_sib1(module_id_t module_idP,
@@ -524,6 +522,7 @@ void schedule_nr_sib1(module_id_t module_idP,
                       nfapi_nr_dl_tti_request_t *DL_req,
                       nfapi_nr_tx_data_request_t *TX_req)
 {
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
   // TODO: Get these values from RRC
   const int CC_id = 0;
   uint8_t candidate_idx = 0;
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
index 71b4499c55a86923e4a849adb030967f9f2eae04..4d3d59c12efd5044c140fb4af1e6cd8deb63b08c 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
@@ -55,6 +55,7 @@
 
 const int get_dl_tda(const gNB_MAC_INST *nrmac, const NR_ServingCellConfigCommon_t *scc, int slot) {
 
+  /* we assume that this function is mutex-protected from outside */
   const NR_TDD_UL_DL_Pattern_t *tdd = scc->tdd_UL_DL_ConfigurationCommon ? &scc->tdd_UL_DL_ConfigurationCommon->pattern1 : NULL;
   AssertFatal(tdd || nrmac->common_channels->frame_type == FDD, "Dynamic TDD not handled yet\n");
 
@@ -76,8 +77,12 @@ int nr_write_ce_dlsch_pdu(module_id_t module_idP,
                           const NR_UE_sched_ctrl_t *ue_sched_ctl,
                           unsigned char *mac_pdu,
                           unsigned char drx_cmd,
-                          unsigned char *ue_cont_res_id) {
+                          unsigned char *ue_cont_res_id)
+{
   gNB_MAC_INST *gNB = RC.nrmac[module_idP];
+  /* already mutex protected: called below and in _RA.c */
+  NR_SCHED_ENSURE_LOCKED(&gNB->sched_lock);
+
   NR_MAC_SUBHEADER_FIXED *mac_pdu_ptr = (NR_MAC_SUBHEADER_FIXED *) mac_pdu;
   uint8_t last_size = 0;
   int offset = 0, mac_ce_size, i, timing_advance_cmd, tag_id = 0;
@@ -311,10 +316,8 @@ int nr_write_ce_dlsch_pdu(module_id_t module_idP,
   return offset;
 }
 
-void nr_store_dlsch_buffer(module_id_t module_id,
-                           frame_t frame,
-                           sub_frame_t slot) {
-
+static void nr_store_dlsch_buffer(module_id_t module_id, frame_t frame, sub_frame_t slot)
+{
   UE_iterator(RC.nrmac[module_id]->UE_info.list, UE) {
     NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
     sched_ctrl->num_total_bytes = 0;
@@ -363,8 +366,9 @@ void nr_store_dlsch_buffer(module_id_t module_id,
   }
 }
 
-void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid) {
-
+void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid)
+{
+  /* already mutex protected through handle_dl_harq() */
   NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
   NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
 
@@ -375,13 +379,13 @@ void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid) {
 
 }
 
-bool allocate_dl_retransmission(module_id_t module_id,
-                                frame_t frame,
-                                sub_frame_t slot,
-                                uint16_t *rballoc_mask,
-                                int *n_rb_sched,
-                                NR_UE_info_t *UE,
-                                int current_harq_pid)
+static bool allocate_dl_retransmission(module_id_t module_id,
+                                       frame_t frame,
+                                       sub_frame_t slot,
+                                       uint16_t *rballoc_mask,
+                                       int *n_rb_sched,
+                                       NR_UE_info_t *UE,
+                                       int current_harq_pid)
 {
 
   int CC_id = 0;
@@ -549,15 +553,14 @@ static int comparator(const void *p, const void *q) {
   return ((UEsched_t*)p)->coef < ((UEsched_t*)q)->coef;
 }
 
-void pf_dl(module_id_t module_id,
-           frame_t frame,
-           sub_frame_t slot,
-           NR_UE_info_t **UE_list,
-           int max_num_ue,
-           int n_rb_sched,
-           uint16_t *rballoc_mask)
+static void pf_dl(module_id_t module_id,
+                  frame_t frame,
+                  sub_frame_t slot,
+                  NR_UE_info_t **UE_list,
+                  int max_num_ue,
+                  int n_rb_sched,
+                  uint16_t *rballoc_mask)
 {
-
   gNB_MAC_INST *mac = RC.nrmac[module_id];
   NR_ServingCellConfigCommon_t *scc=mac->common_channels[0].ServingCellConfigCommon;
   // UEs that could be scheduled
@@ -784,7 +787,7 @@ void pf_dl(module_id_t module_id,
   }
 }
 
-void nr_fr1_dlsch_preprocessor(module_id_t module_id, frame_t frame, sub_frame_t slot)
+static void nr_fr1_dlsch_preprocessor(module_id_t module_id, frame_t frame, sub_frame_t slot)
 {
   NR_UEs_t *UE_info = &RC.nrmac[module_id]->UE_info;
 
@@ -843,6 +846,7 @@ void nr_fr1_dlsch_preprocessor(module_id_t module_id, frame_t frame, sub_frame_t
 }
 
 nr_pp_impl_dl nr_init_fr1_dlsch_preprocessor(int CC_id) {
+  /* during initialization: no mutex needed */
   /* in the PF algorithm, we have to use the TBsize to compute the coefficient.
    * This would include the number of DMRS symbols, which in turn depends on
    * the time domain allocation. In case we are in a mixed slot, we do not want
@@ -876,6 +880,9 @@ void nr_schedule_ue_spec(module_id_t module_id,
                          nfapi_nr_tx_data_request_t *TX_req)
 {
   gNB_MAC_INST *gNB_mac = RC.nrmac[module_id];
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  AssertFatal(pthread_mutex_trylock(&gNB_mac->sched_lock) == EBUSY,
+              "this function should be called with the scheduler mutex locked\n");
 
   if (!is_xlsch_in_slot(gNB_mac->dlsch_slot_bitmap[slot / 64], slot))
     return;
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c
index 96756d41cdfee6b9cee3a14bde31f9f646b5c00f..d59efafdc24c0c47a51e2d753adf7d7fa40f4de3 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c
@@ -50,6 +50,7 @@ void nr_preprocessor_phytest(module_id_t module_id,
                              frame_t frame,
                              sub_frame_t slot)
 {
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
   if (!is_xlsch_in_slot(dlsch_slot_bitmap, slot))
     return;
   NR_UE_info_t *UE = RC.nrmac[module_id]->UE_info.list[0];
@@ -192,6 +193,7 @@ uint64_t ulsch_slot_bitmap = (1 << 8);
 bool nr_ul_preprocessor_phytest(module_id_t module_id, frame_t frame, sub_frame_t slot)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_id];
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
   NR_COMMON_channels_t *cc = nr_mac->common_channels;
   NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
   NR_UE_info_t *UE = nr_mac->UE_info.list[0];
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
index 0deb86a5dbd7e64ced31c33f677cedb542138f09..2a8457356a76bd47db720eff56cd5978fbcba414 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
@@ -509,7 +509,7 @@ void fill_pdcch_vrb_map(gNB_MAC_INST *mac,
   }
 }
 
-bool multiple_2_3_5(int rb)
+static bool multiple_2_3_5(int rb)
 {
   while (rb % 2 == 0)
     rb /= 2;
@@ -2369,7 +2369,7 @@ NR_UE_info_t *add_new_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rntiP, NR_CellGroupConf
 
   reset_srs_stats(UE);
 
-  pthread_mutex_lock(&UE_info->mutex);
+  NR_SCHED_LOCK(&UE_info->mutex);
   int i;
   for(i=0; i<MAX_MOBILES_PER_GNB; i++) {
     if (UE_info->list[i] == NULL) {
@@ -2380,10 +2380,10 @@ NR_UE_info_t *add_new_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rntiP, NR_CellGroupConf
   if (i == MAX_MOBILES_PER_GNB) {
     LOG_E(NR_MAC,"Try to add UE %04x but the list is full\n", rntiP);
     delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator);
-    pthread_mutex_unlock(&UE_info->mutex);
+    NR_SCHED_UNLOCK(&UE_info->mutex);
     return NULL;
   }
-  pthread_mutex_unlock(&UE_info->mutex);
+  NR_SCHED_UNLOCK(&UE_info->mutex);
 
   LOG_D(NR_MAC, "Add NR rnti %x\n", rntiP);
   dump_nr_list(UE_info->list);
@@ -2487,8 +2487,11 @@ void reset_ul_harq_list(NR_UE_sched_ctrl_t *sched_ctrl) {
 
 void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti)
 {
+  /* already mutex protected */
+  NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock);
+
   NR_UEs_t *UE_info = &nr_mac->UE_info;
-  pthread_mutex_lock(&UE_info->mutex);
+  NR_SCHED_LOCK(&UE_info->mutex);
   UE_iterator(UE_info->list, UE) {
     if (UE->rnti==rnti)
       break;
@@ -2496,7 +2499,7 @@ void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti)
 
   if (!UE) {
     LOG_W(NR_MAC,"Call to del rnti %04x, but not existing\n", rnti);
-    pthread_mutex_unlock(&UE_info->mutex);
+    NR_SCHED_UNLOCK(&UE_info->mutex);
     return;
   }
 
@@ -2506,7 +2509,7 @@ void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti)
     if(UE_info->list[i] && UE_info->list[i]->rnti != rnti)
       newUEs[newListIdx++]=UE_info->list[i];
   memcpy(UE_info->list, newUEs, sizeof(UE_info->list));
-  pthread_mutex_unlock(&UE_info->mutex);
+  NR_SCHED_UNLOCK(&UE_info->mutex);
 
   delete_nr_ue_data(UE, nr_mac->common_channels, &UE_info->uid_allocator);
 }
@@ -2525,6 +2528,7 @@ uint8_t nr_get_tpc(int target, uint8_t cqi, int incr) {
 int get_pdsch_to_harq_feedback(NR_PUCCH_Config_t *pucch_Config,
                                 nr_dci_format_t dci_format,
                                 uint8_t *pdsch_to_harq_feedback) {
+  /* already mutex protected: held in nr_acknack_scheduling() */
 
   if (dci_format == NR_DL_DCI_FORMAT_1_0) {
     for (int i = 0; i < 8; i++)
@@ -2545,6 +2549,9 @@ void nr_csirs_scheduling(int Mod_idP, frame_t frame, sub_frame_t slot, int n_slo
   int CC_id = 0;
   NR_UEs_t *UE_info = &RC.nrmac[Mod_idP]->UE_info;
   gNB_MAC_INST *gNB_mac = RC.nrmac[Mod_idP];
+
+  NR_SCHED_ENSURE_LOCKED(&gNB_mac->sched_lock);
+
   uint16_t *vrb_map = gNB_mac->common_channels[CC_id].vrb_map;
 
   UE_info->sched_csirs = false;
@@ -2770,7 +2777,10 @@ void nr_csirs_scheduling(int Mod_idP, frame_t frame, sub_frame_t slot, int n_slo
 
 void nr_mac_update_timers(module_id_t module_id,
                           frame_t frame,
-                          sub_frame_t slot) {
+                          sub_frame_t slot)
+{
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&RC.nrmac[module_id]->sched_lock);
 
   NR_UEs_t *UE_info = &RC.nrmac[module_id]->UE_info;
   UE_iterator(UE_info->list, UE) {
@@ -2824,7 +2834,10 @@ void nr_mac_update_timers(module_id_t module_id,
 
 void schedule_nr_bwp_switch(module_id_t module_id,
                             frame_t frame,
-                            sub_frame_t slot) {
+                            sub_frame_t slot)
+{
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&RC.nrmac[module_id]->sched_lock);
 
   NR_UEs_t *UE_info = &RC.nrmac[module_id]->UE_info;
 
@@ -2887,6 +2900,7 @@ void send_initial_ul_rrc_message(gNB_MAC_INST *mac, int rnti, const uint8_t *sdu
   LOG_W(MAC, "[RAPROC] Received SDU for CCCH length %d for UE %04x\n", sdu_len, rnti);
 
   NR_UE_info_t *UE = (NR_UE_info_t *)rawUE;
+  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);
 
   uint8_t du2cu[1024];
   int encoded = encode_cellGroupConfig(UE->CellGroup, du2cu, sizeof(du2cu));
@@ -2904,6 +2918,7 @@ void send_initial_ul_rrc_message(gNB_MAC_INST *mac, int rnti, const uint8_t *sdu
 
 void prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE)
 {
+  NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);
   /* create this UE's initial CellGroup */
   /* Note: relying on the RRC is a hack, as we are in the DU; there should be
    * no RRC, remove in the future */
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c
index 8a85448c7fb23f415910bf07acfe0f1179d69d3a..3309e1af34fad78f8623bc79bbe18e35a24baa53 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_srs.c
@@ -42,7 +42,8 @@ const uint16_t m_SRS[64] = { 4, 8, 12, 16, 16, 20, 24, 24, 28, 32, 36, 40, 48, 4
                              160, 160, 168, 176, 184, 192, 192, 192, 192, 208, 216, 224, 240, 240, 240, 240, 256, 256,
                              256, 264, 272, 272, 272 };
 
-uint32_t max4(uint32_t a, uint32_t b,uint32_t c,uint32_t d) {
+static uint32_t max4(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
   int x = max(a, b);
   x = max(x, c);
   x = max(x, d);
@@ -53,6 +54,9 @@ void nr_srs_ri_computation(const nfapi_nr_srs_normalized_channel_iq_matrix_t *nr
                            const NR_UE_UL_BWP_t *current_BWP,
                            uint8_t *ul_ri)
 {
+  /* already mutex protected: held in handle_nr_srs_measurements() */
+  NR_SCHED_ENSURE_LOCKED(&RC.nrmac[0]->sched_lock);
+
   // If the gNB or UE has 1 antenna, the rank is always 1, i.e., *ul_ri = 0.
   // For 2x2 scenario, we compute the rank of channel.
   // The computation for 2x4, 4x2, 4x4, ... scenarios are not implemented yet. In these cases, the function sets *ul_ri = 0, which is always a valid value.
@@ -137,7 +141,14 @@ void nr_srs_ri_computation(const nfapi_nr_srs_normalized_channel_iq_matrix_t *nr
 
 }
 
-void nr_configure_srs(nfapi_nr_srs_pdu_t *srs_pdu, int slot, int module_id, int CC_id, NR_UE_info_t *UE, NR_SRS_ResourceSet_t *srs_resource_set, NR_SRS_Resource_t *srs_resource, int buffer_index)
+static void nr_configure_srs(nfapi_nr_srs_pdu_t *srs_pdu,
+                             int slot,
+                             int module_id,
+                             int CC_id,
+                             NR_UE_info_t *UE,
+                             NR_SRS_ResourceSet_t *srs_resource_set,
+                             NR_SRS_Resource_t *srs_resource,
+                             int buffer_index)
 {
   NR_UE_UL_BWP_t *current_BWP = &UE->current_UL_BWP;
 
@@ -196,7 +207,13 @@ void nr_configure_srs(nfapi_nr_srs_pdu_t *srs_pdu, int slot, int module_id, int
     vrb_map_UL[i + srs_pdu->bwp_start] |= mask;
 }
 
-void nr_fill_nfapi_srs(int module_id, int CC_id, NR_UE_info_t* UE, int frame, int slot, NR_SRS_ResourceSet_t *srs_resource_set, NR_SRS_Resource_t *srs_resource)
+static void nr_fill_nfapi_srs(int module_id,
+                              int CC_id,
+                              NR_UE_info_t *UE,
+                              int frame,
+                              int slot,
+                              NR_SRS_ResourceSet_t *srs_resource_set,
+                              NR_SRS_Resource_t *srs_resource)
 {
 
   int index = ul_buffer_index(frame, slot, UE->current_UL_BWP.scs, RC.nrmac[module_id]->UL_tti_req_ahead_size);
@@ -226,8 +243,10 @@ void nr_fill_nfapi_srs(int module_id, int CC_id, NR_UE_info_t* UE, int frame, in
 *********************************************************************/
 void nr_schedule_srs(int module_id, frame_t frame, int slot)
  {
-
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
   gNB_MAC_INST *nrmac = RC.nrmac[module_id];
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
+
   NR_UEs_t *UE_info = &nrmac->UE_info;
 
   UE_iterator(UE_info->list, UE) {
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
index bc1796a6dc5933ab70b85ef01466387092666057..e1716458d23ad5195536247bcef2285099cd8ba5 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
@@ -144,7 +144,7 @@ static const int diff_rsrp_ssb_csi_meas_10_1_6_1_2[16] = {
     -30 // 10 - 15
 };
 
-int get_pucch_index(int frame, int slot, int n_slots_frame, const NR_TDD_UL_DL_Pattern_t *tdd, int sched_pucch_size)
+static int get_pucch_index(int frame, int slot, int n_slots_frame, const NR_TDD_UL_DL_Pattern_t *tdd, int sched_pucch_size)
 {
   // PUCCH structures are indexed by slot in the PUCCH period determined by sched_pucch_size number of UL slots
   // this functions return the index to the structure for slot passed to the function
@@ -167,6 +167,9 @@ void nr_schedule_pucch(gNB_MAC_INST *nrmac,
                        frame_t frameP,
                        sub_frame_t slotP)
 {
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
+
   if (!is_xlsch_in_slot(nrmac->ulsch_slot_bitmap[slotP / 64], slotP))
     return;
 
@@ -198,7 +201,10 @@ void nr_csi_meas_reporting(int Mod_idP,
                            frame_t frame,
                            sub_frame_t slot)
 {
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
   gNB_MAC_INST *nrmac = RC.nrmac[Mod_idP];
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
+
   UE_iterator(nrmac->UE_info.list, UE ) {
     NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
     NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
@@ -324,8 +330,8 @@ static void handle_dl_harq(NR_UE_info_t * UE,
   }
 }
 
-int checkTargetSSBInFirst64TCIStates_pdschConfig(int ssb_index_t, NR_UE_info_t * UE) {
-
+static int checkTargetSSBInFirst64TCIStates_pdschConfig(int ssb_index_t, NR_UE_info_t *UE)
+{
   const NR_PDSCH_Config_t *pdsch_Config = UE->current_DL_BWP.pdsch_Config;
   int nb_tci_states = pdsch_Config ? pdsch_Config->tci_StatesToAddModList->list.count : 0;
   NR_TCI_State_t *tci =NULL;
@@ -350,8 +356,8 @@ int checkTargetSSBInFirst64TCIStates_pdschConfig(int ssb_index_t, NR_UE_info_t *
   return -1;
 }
 
-int checkTargetSSBInTCIStates_pdcchConfig(int ssb_index_t, NR_UE_info_t *UE) {
-
+static int checkTargetSSBInTCIStates_pdcchConfig(int ssb_index_t, NR_UE_info_t *UE)
+{
   NR_TCI_State_t *tci =NULL;
   NR_TCI_StateId_t *tci_id = NULL;
   NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
@@ -390,7 +396,8 @@ int checkTargetSSBInTCIStates_pdcchConfig(int ssb_index_t, NR_UE_info_t *UE) {
 }
 
 //returns the measured RSRP value (upper limit)
-int get_measured_rsrp(uint8_t index) {
+static int get_measured_rsrp(uint8_t index)
+{
   //if index is invalid returning minimum rsrp -140
   if(index <= 15 || index >= 114)
     return MIN_RSRP_VALUE;
@@ -399,7 +406,7 @@ int get_measured_rsrp(uint8_t index) {
 }
 
 //returns the differential RSRP value (upper limit)
-int get_diff_rsrp(uint8_t index, int strongest_rsrp) {
+static int get_diff_rsrp(uint8_t index, int strongest_rsrp) {
   if(strongest_rsrp != -1) {
     return strongest_rsrp + diff_rsrp_ssb_csi_meas_10_1_6_1_2[index];
   } else
@@ -409,8 +416,8 @@ int get_diff_rsrp(uint8_t index, int strongest_rsrp) {
 //identifies the target SSB Beam index
 //keeps the required date for PDCCH and PDSCH TCI state activation/deactivation CE consutruction globally
 //handles triggering of PDCCH and PDSCH MAC CEs
-void tci_handling(NR_UE_info_t *UE, frame_t frame, slot_t slot) {
-
+static void tci_handling(NR_UE_info_t *UE, frame_t frame, slot_t slot)
+{
   int strongest_ssb_rsrp = 0;
   int cqi_idx = 0;
   int curr_ssb_beam_index = 0; //ToDo: yet to know how to identify the serving ssb beam index
@@ -583,24 +590,23 @@ void tci_handling(NR_UE_info_t *UE, frame_t frame, slot_t slot) {
       }
     }//tci_presentInDCI
   }//is-triggering_beam_switch
-}//tci handling
+} // tci handling
 
-
-uint8_t pickandreverse_bits(uint8_t *payload, uint16_t bitlen, uint8_t start_bit) {
+static uint8_t pickandreverse_bits(uint8_t *payload, uint16_t bitlen, uint8_t start_bit)
+{
   uint8_t rev_bits = 0;
   for (int i=0; i<bitlen; i++)
     rev_bits |= ((payload[(start_bit+i)/8]>>((start_bit+i)%8))&0x01)<<(bitlen-i-1);
   return rev_bits;
 }
 
-
-void evaluate_rsrp_report(NR_UE_info_t *UE,
-                          NR_UE_sched_ctrl_t *sched_ctrl,
-                          uint8_t csi_report_id,
-                          uint8_t *payload,
-                          int *cumul_bits,
-                          NR_CSI_ReportConfig__reportQuantity_PR reportQuantity_type){
-
+static void evaluate_rsrp_report(NR_UE_info_t *UE,
+                                 NR_UE_sched_ctrl_t *sched_ctrl,
+                                 uint8_t csi_report_id,
+                                 uint8_t *payload,
+                                 int *cumul_bits,
+                                 NR_CSI_ReportConfig__reportQuantity_PR reportQuantity_type)
+{
   nr_csi_report_t *csi_report = &UE->csi_report_template[csi_report_id];
   uint8_t cri_ssbri_bitlen = csi_report->CSI_report_bitlen.cri_ssbri_bitlen;
   uint16_t curr_payload;
@@ -659,21 +665,18 @@ void evaluate_rsrp_report(NR_UE_info_t *UE,
   stats->num_rsrp_meas++;
 }
 
-void evaluate_cri_report(uint8_t *payload,
-                         uint8_t cri_bitlen,
-                         int cumul_bits,
-                         NR_UE_sched_ctrl_t *sched_ctrl){
-
+static void evaluate_cri_report(uint8_t *payload, uint8_t cri_bitlen, int cumul_bits, NR_UE_sched_ctrl_t *sched_ctrl)
+{
   uint8_t temp_cri = pickandreverse_bits(payload, cri_bitlen, cumul_bits);
   sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.cri = temp_cri;
 }
 
-int evaluate_ri_report(uint8_t *payload,
-                       uint8_t ri_bitlen,
-                       uint8_t ri_restriction,
-                       int cumul_bits,
-                       NR_UE_sched_ctrl_t *sched_ctrl){
-
+static int evaluate_ri_report(uint8_t *payload,
+                              uint8_t ri_bitlen,
+                              uint8_t ri_restriction,
+                              int cumul_bits,
+                              NR_UE_sched_ctrl_t *sched_ctrl)
+{
   uint8_t ri_index = pickandreverse_bits(payload, ri_bitlen, cumul_bits);
   int count=0;
   for (int i=0; i<8; i++) {
@@ -689,13 +692,12 @@ int evaluate_ri_report(uint8_t *payload,
   AssertFatal(1==0, "Decoded ri %d does not correspond to any valid value in ri_restriction %d\n",ri_index,ri_restriction);
 }
 
-
-void evaluate_cqi_report(uint8_t *payload,
-                         nr_csi_report_t *csi_report,
-                         int cumul_bits,
-                         uint8_t ri,
-                         NR_UE_info_t *UE,
-                         uint8_t cqi_Table)
+static void evaluate_cqi_report(uint8_t *payload,
+                                nr_csi_report_t *csi_report,
+                                int cumul_bits,
+                                uint8_t ri,
+                                NR_UE_info_t *UE,
+                                uint8_t cqi_Table)
 {
 
   NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
@@ -723,13 +725,12 @@ void evaluate_cqi_report(uint8_t *payload,
   sched_ctrl->dl_max_mcs = get_mcs_from_cqi(mcs_table, cqi_Table, cqi_idx);
 }
 
-
-uint8_t evaluate_pmi_report(uint8_t *payload,
-                            nr_csi_report_t *csi_report,
-                            int cumul_bits,
-                            uint8_t ri,
-                            NR_UE_sched_ctrl_t *sched_ctrl){
-
+static uint8_t evaluate_pmi_report(uint8_t *payload,
+                                   nr_csi_report_t *csi_report,
+                                   int cumul_bits,
+                                   uint8_t ri,
+                                   NR_UE_sched_ctrl_t *sched_ctrl)
+{
   int x1_bitlen = csi_report->csi_meas_bitlen.pmi_x1_bitlen[ri];
   int x2_bitlen = csi_report->csi_meas_bitlen.pmi_x2_bitlen[ri];
   int tot_bitlen = x1_bitlen + x2_bitlen;
@@ -744,16 +745,14 @@ uint8_t evaluate_pmi_report(uint8_t *payload,
         sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2);
 
   return tot_bitlen;
-
 }
 
-
-int evaluate_li_report(uint8_t *payload,
-                       nr_csi_report_t *csi_report,
-                       int cumul_bits,
-                       uint8_t ri,
-                       NR_UE_sched_ctrl_t *sched_ctrl){
-
+static int evaluate_li_report(uint8_t *payload,
+                              nr_csi_report_t *csi_report,
+                              int cumul_bits,
+                              uint8_t ri,
+                              NR_UE_sched_ctrl_t *sched_ctrl)
+{
   int li_bitlen = csi_report->csi_meas_bitlen.li_bitlen[ri];
 
   if (li_bitlen>0) {
@@ -762,14 +761,10 @@ int evaluate_li_report(uint8_t *payload,
     sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.li = temp_li;
   }
   return li_bitlen;
-
 }
 
-void skip_zero_padding(int *cumul_bits,
-                       nr_csi_report_t *csi_report,
-                       uint8_t ri,
-                       uint16_t max_bitlen) {
-
+static void skip_zero_padding(int *cumul_bits, nr_csi_report_t *csi_report, uint8_t ri, uint16_t max_bitlen)
+{
   // actual number of reported bits depends on the reported rank
   // zero padding bits are added to have a predetermined max bit length to decode
 
@@ -783,13 +778,12 @@ void skip_zero_padding(int *cumul_bits,
   *cumul_bits+=(max_bitlen-reported_bitlen);
 }
 
-
-void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
-                              const nfapi_nr_uci_pucch_pdu_format_2_3_4_t *uci_pdu,
-                              frame_t frame,
-                              slot_t slot,
-                              NR_UE_info_t *UE,
-                              NR_ServingCellConfigCommon_t *scc)
+static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
+                                     const nfapi_nr_uci_pucch_pdu_format_2_3_4_t *uci_pdu,
+                                     frame_t frame,
+                                     slot_t slot,
+                                     NR_UE_info_t *UE,
+                                     NR_ServingCellConfigCommon_t *scc)
 {
   /** From Table 6.3.1.1.2-3: RI, LI, CQI, and CRI of codebookType=typeI-SinglePanel */
   uint8_t *payload = uci_pdu->csi_part1.csi_part1_payload;
@@ -929,9 +923,12 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id,
                              sub_frame_t slot,
                              const nfapi_nr_uci_pucch_pdu_format_0_1_t *uci_01)
 {
-  NR_UE_info_t * UE = find_nr_UE(&RC.nrmac[mod_id]->UE_info, uci_01->rnti);
+  gNB_MAC_INST *nrmac = RC.nrmac[mod_id];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
+  NR_UE_info_t * UE = find_nr_UE(&nrmac->UE_info, uci_01->rnti);
   if (!UE) {
     LOG_E(NR_MAC, "%s(): unknown RNTI %04x in PUCCH UCI\n", __func__, uci_01->rnti);
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     return;
   }
   NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
@@ -941,7 +938,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id,
     for (int harq_bit = 0; harq_bit < uci_01->harq->num_harq; harq_bit++) {
       const uint8_t harq_value = uci_01->harq->harq_list[harq_bit].harq_value;
       const uint8_t harq_confidence = uci_01->harq->harq_confidence_level;
-      NR_UE_harq_t *harq = find_harq(frame, slot, UE, RC.nrmac[mod_id]->dl_bler.harq_round_max);
+      NR_UE_harq_t *harq = find_harq(frame, slot, UE, nrmac->dl_bler.harq_round_max);
       if (!harq) {
         LOG_E(NR_MAC, "Oh no! Could not find a harq in %s!\n", __FUNCTION__);
         break;
@@ -950,13 +947,13 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id,
       const int8_t pid = sched_ctrl->feedback_dl_harq.head;
       remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
       LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit,pid,harq_value);
-      handle_dl_harq(UE, pid, harq_value == 0 && harq_confidence == 0, RC.nrmac[mod_id]->dl_bler.harq_round_max);
+      handle_dl_harq(UE, pid, harq_value == 0 && harq_confidence == 0, nrmac->dl_bler.harq_round_max);
       if (harq_confidence == 1)  UE->mac_stats.pucch0_DTX++;
     }
 
     // tpc (power control) only if we received AckNack
     if (uci_01->harq->harq_confidence_level==0)
-      sched_ctrl->tpc1 = nr_get_tpc(RC.nrmac[mod_id]->pucch_target_snrx10, uci_01->ul_cqi, 30);
+      sched_ctrl->tpc1 = nr_get_tpc(nrmac->pucch_target_snrx10, uci_01->ul_cqi, 30);
     else
       sched_ctrl->tpc1 = 3;
     sched_ctrl->pucch_snrx10 = uci_01->ul_cqi * 5 - 640;
@@ -974,6 +971,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id,
     }
     free(uci_01->sr);
   }
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
@@ -981,15 +979,21 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
                                sub_frame_t slot,
                                const nfapi_nr_uci_pucch_pdu_format_2_3_4_t *uci_234)
 {
-  NR_UE_info_t * UE = find_nr_UE(&RC.nrmac[mod_id]->UE_info, uci_234->rnti);
+  gNB_MAC_INST *nrmac = RC.nrmac[mod_id];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
+
+  NR_UE_info_t * UE = find_nr_UE(&nrmac->UE_info, uci_234->rnti);
   if (!UE) {
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     LOG_E(NR_MAC, "%s(): unknown RNTI %04x in PUCCH UCI\n", __func__, uci_234->rnti);
     return;
   }
 
   NR_CSI_MeasConfig_t *csi_MeasConfig = UE->current_UL_BWP.csi_MeasConfig;
-  if (csi_MeasConfig==NULL)
+  if (csi_MeasConfig==NULL) {
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     return;
+  }
   NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
 
   // tpc (power control)
@@ -1014,13 +1018,13 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
       DevAssert(harq->is_waiting);
       const int8_t pid = sched_ctrl->feedback_dl_harq.head;
       remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
-      handle_dl_harq(UE, pid, uci_234->harq.harq_crc != 1 && acknack, RC.nrmac[mod_id]->dl_bler.harq_round_max);
+      handle_dl_harq(UE, pid, uci_234->harq.harq_crc != 1 && acknack, nrmac->dl_bler.harq_round_max);
     }
     free(uci_234->harq.harq_payload);
   }
   if ((uci_234->pduBitmap >> 2) & 0x01) {
     //API to parse the csi report and store it into sched_ctrl
-    extract_pucch_csi_report(csi_MeasConfig, uci_234, frame, slot, UE, RC.nrmac[mod_id]->common_channels->ServingCellConfigCommon);
+    extract_pucch_csi_report(csi_MeasConfig, uci_234, frame, slot, UE, nrmac->common_channels->ServingCellConfigCommon);
     //TCI handling function
     tci_handling(UE,frame, slot);
     free(uci_234->csi_part1.csi_part1_payload);
@@ -1029,9 +1033,10 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
     //@TODO:Handle CSI Report 2
     // nothing to free (yet)
   }
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
-void set_pucch_allocation(const NR_UE_UL_BWP_t *ul_bwp, const int r_pucch, const int bwp_size, NR_sched_pucch_t *pucch)
+static void set_pucch_allocation(const NR_UE_UL_BWP_t *ul_bwp, const int r_pucch, const int bwp_size, NR_sched_pucch_t *pucch)
 {
   if(r_pucch<0){
     const NR_PUCCH_Resource_t *resource = ul_bwp->pucch_Config->resourceToAddModList->list.array[0];
@@ -1053,11 +1058,8 @@ void set_pucch_allocation(const NR_UE_UL_BWP_t *ul_bwp, const int r_pucch, const
   }
 }
 
-bool test_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch,
-                                uint16_t *vrb_map_UL,
-                                const int bwp_start,
-                                const int bwp_size) {
-
+static bool test_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch, uint16_t *vrb_map_UL, const int bwp_start, const int bwp_size)
+{
   // We assume initial cyclic shift is always 0 so different pucch resources can't overlap
 
   // verifying occupation of PRBs for ACK/NACK on dedicated pucch
@@ -1076,9 +1078,7 @@ bool test_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch,
   return true;
 }
 
-void set_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch,
-                               uint16_t *vrb_map_UL,
-                               const int bwp_start)
+static void set_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch, uint16_t *vrb_map_UL, const int bwp_start)
 {
   for (int l=0; l<pucch->nr_of_symb; l++) {
     uint16_t symb = SL_to_bitmap(pucch->start_symb+l, 1);
@@ -1100,6 +1100,8 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
                           int r_pucch,
                           int is_common)
 {
+  /* we assume that this function is mutex-protected from outside. Since it is
+   * called often, don't try to lock every time */
 
   const int CC_id = 0;
   const int minfbtime = mac->minRXTXTIMEpdsch;
@@ -1204,6 +1206,9 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
 
 void nr_sr_reporting(gNB_MAC_INST *nrmac, frame_t SFN, sub_frame_t slot)
 {
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
+
   if (!is_xlsch_in_slot(nrmac->ulsch_slot_bitmap[slot / 64], slot))
     return;
   const int CC_id = 0;
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
index e1a4e716efe4acd0f76df9d660b0a88fbf83c70a..80c1652b61caa1aeb3d41c2f13d02e18619e86de 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c
@@ -39,7 +39,10 @@
 
 //#define SRS_IND_DEBUG
 
-const int get_ul_tda(gNB_MAC_INST *nrmac, const NR_ServingCellConfigCommon_t *scc, int frame, int slot) {
+const int get_ul_tda(gNB_MAC_INST *nrmac, const NR_ServingCellConfigCommon_t *scc, int frame, int slot)
+{
+  /* we assume that this function is mutex-protected from outside */
+  NR_SCHED_ENSURE_LOCKED(&nrmac->sched_lock);
 
   /* there is a mixed slot only when in TDD */
   const NR_TDD_UL_DL_Pattern_t *tdd = scc->tdd_UL_DL_ConfigurationCommon ? &scc->tdd_UL_DL_ConfigurationCommon->pattern1 : NULL;
@@ -61,14 +64,8 @@ const int get_ul_tda(gNB_MAC_INST *nrmac, const NR_ServingCellConfigCommon_t *sc
   return 0; // if FDD or not mixed slot in TDD, for now use default TDA (TODO handle CSI-RS slots)
 }
 
-int compute_ph_factor(int mu,
-                      int tbs_bits,
-                      int rb,
-                      int n_layers,
-                      int n_symbols,
-                      int n_dmrs,
-                      long *deltaMCS) {
-
+static int compute_ph_factor(int mu, int tbs_bits, int rb, int n_layers, int n_symbols, int n_dmrs, long *deltaMCS)
+{
   // 38.213 7.1.1
   // if the PUSCH transmission is over more than one layer delta_tf = 0
   int delta_tf = 0;
@@ -109,14 +106,14 @@ int compute_ph_factor(int mu,
 //  F: length of L is 0:8 or 1:16 bits wide
 //  R: Reserved bit, set to zero.
 
-int nr_process_mac_pdu(instance_t module_idP,
-                       NR_UE_info_t* UE,
-                       uint8_t CC_id,
-                       frame_t frameP,
-                       sub_frame_t slot,
-                       uint8_t *pduP,
-                       int pdu_len,
-                       const int8_t harq_pid)
+static int nr_process_mac_pdu(instance_t module_idP,
+                              NR_UE_info_t *UE,
+                              uint8_t CC_id,
+                              frame_t frameP,
+                              sub_frame_t slot,
+                              uint8_t *pduP,
+                              int pdu_len,
+                              const int8_t harq_pid)
 {
 
   uint8_t done = 0;
@@ -462,7 +459,7 @@ int nr_process_mac_pdu(instance_t module_idP,
   return 0;
 }
 
-void abort_nr_ul_harq(NR_UE_info_t *UE, int8_t harq_pid)
+static void abort_nr_ul_harq(NR_UE_info_t *UE, int8_t harq_pid)
 {
   NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
   NR_UE_ul_harq_t *harq = &sched_ctrl->ul_harq_processes[harq_pid];
@@ -479,7 +476,7 @@ void abort_nr_ul_harq(NR_UE_info_t *UE, int8_t harq_pid)
     sched_ctrl->sched_ul_bytes = 0;
 }
 
-bool get_UE_waiting_CFRA_msg3(const gNB_MAC_INST *gNB_mac, const int CC_id, const frame_t frame, const sub_frame_t slot)
+static bool get_UE_waiting_CFRA_msg3(const gNB_MAC_INST *gNB_mac, const int CC_id, const frame_t frame, const sub_frame_t slot)
 {
   bool UE_waiting_CFRA_msg3 = false;
   for (int i = 0; i < NR_NB_RA_PROC_MAX; i++) {
@@ -498,17 +495,22 @@ void handle_nr_ul_harq(const int CC_idP,
                        sub_frame_t slot,
                        const nfapi_nr_crc_t *crc_pdu)
 {
-  NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[mod_id]->UE_info, crc_pdu->rnti);
-  bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(RC.nrmac[mod_id], CC_idP, frame, slot);
+  gNB_MAC_INST *nrmac = RC.nrmac[mod_id];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
+
+  NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, crc_pdu->rnti);
+  bool UE_waiting_CFRA_msg3 = get_UE_waiting_CFRA_msg3(nrmac, CC_idP, frame, slot);
 
   if (!UE || UE_waiting_CFRA_msg3 == true) {
     LOG_W(NR_MAC, "handle harq for rnti %04x, in RA process\n", crc_pdu->rnti);
     for (int i = 0; i < NR_NB_RA_PROC_MAX; ++i) {
-      NR_RA_t *ra = &RC.nrmac[mod_id]->common_channels[CC_idP].ra[i];
-      if (ra->state >= WAIT_Msg3 &&
-          ra->rnti == crc_pdu->rnti)
+      NR_RA_t *ra = &nrmac->common_channels[CC_idP].ra[i];
+      if (ra->state >= WAIT_Msg3 && ra->rnti == crc_pdu->rnti) {
+        NR_SCHED_UNLOCK(&nrmac->sched_lock);
         return;
+      }
     }
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     LOG_E(NR_MAC, "%s(): unknown RNTI 0x%04x in PUSCH\n", __func__, crc_pdu->rnti);
     return;
   }
@@ -521,8 +523,10 @@ void handle_nr_ul_harq(const int CC_idP,
           crc_pdu->harq_id,
           harq_pid,
           crc_pdu->rnti);
-    if (harq_pid < 0)
+    if (harq_pid < 0) {
+      NR_SCHED_UNLOCK(&nrmac->sched_lock);
       return;
+    }
 
     remove_front_nr_list(&sched_ctrl->feedback_ul_harq);
     sched_ctrl->ul_harq_processes[harq_pid].is_waiting = false;
@@ -562,22 +566,23 @@ void handle_nr_ul_harq(const int CC_idP,
           crc_pdu->rnti);
     add_tail_nr_list(&sched_ctrl->retrans_ul_harq, harq_pid);
   }
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 /*
 * When data are received on PHY and transmitted to MAC
 */
-void nr_rx_sdu(const module_id_t gnb_mod_idP,
-               const int CC_idP,
-               const frame_t frameP,
-               const sub_frame_t slotP,
-               const rnti_t rntiP,
-               uint8_t *sduP,
-               const uint16_t sdu_lenP,
-               const uint16_t timing_advance,
-               const uint8_t ul_cqi,
-               const uint16_t rssi){
-
+static void _nr_rx_sdu(const module_id_t gnb_mod_idP,
+                       const int CC_idP,
+                       const frame_t frameP,
+                       const sub_frame_t slotP,
+                       const rnti_t rntiP,
+                       uint8_t *sduP,
+                       const uint16_t sdu_lenP,
+                       const uint16_t timing_advance,
+                       const uint8_t ul_cqi,
+                       const uint16_t rssi)
+{
   gNB_MAC_INST *gNB_mac = RC.nrmac[gnb_mod_idP];
 
   const int current_rnti = rntiP;
@@ -841,8 +846,25 @@ void nr_rx_sdu(const module_id_t gnb_mod_idP,
   }
 }
 
-uint32_t calc_power_complex(const int16_t *x, const int16_t *y, const uint32_t size) {
+void nr_rx_sdu(const module_id_t gnb_mod_idP,
+               const int CC_idP,
+               const frame_t frameP,
+               const sub_frame_t slotP,
+               const rnti_t rntiP,
+               uint8_t *sduP,
+               const uint16_t sdu_lenP,
+               const uint16_t timing_advance,
+               const uint8_t ul_cqi,
+               const uint16_t rssi)
+{
+  gNB_MAC_INST *gNB_mac = RC.nrmac[gnb_mod_idP];
+  NR_SCHED_LOCK(&gNB_mac->sched_lock);
+  _nr_rx_sdu(gnb_mod_idP, CC_idP, frameP, slotP, rntiP, sduP, sdu_lenP, timing_advance, ul_cqi, rssi);
+  NR_SCHED_UNLOCK(&gNB_mac->sched_lock);
+}
 
+static uint32_t calc_power_complex(const int16_t *x, const int16_t *y, const uint32_t size)
+{
   // Real part value
   int64_t sum_x = 0;
   int64_t sum_x2 = 0;
@@ -864,7 +886,8 @@ uint32_t calc_power_complex(const int16_t *x, const int16_t *y, const uint32_t s
   return power_re+power_im;
 }
 
-c16_t nr_h_times_w(c16_t h, char w) {
+static c16_t nr_h_times_w(c16_t h, char w)
+{
   c16_t output;
     switch (w) {
       case '0': // 0
@@ -893,11 +916,11 @@ c16_t nr_h_times_w(c16_t h, char w) {
   return output;
 }
 
-uint8_t get_max_tpmi(const NR_PUSCH_Config_t *pusch_Config,
-                     const uint16_t num_ue_srs_ports,
-                     const uint8_t *nrOfLayers,
-                     int *additional_max_tpmi) {
-
+static uint8_t get_max_tpmi(const NR_PUSCH_Config_t *pusch_Config,
+                            const uint16_t num_ue_srs_ports,
+                            const uint8_t *nrOfLayers,
+                            int *additional_max_tpmi)
+{
   uint8_t max_tpmi = 0;
 
   if ((pusch_Config && pusch_Config->txConfig != NULL && *pusch_Config->txConfig == NR_PUSCH_Config__txConfig_nonCodebook) ||
@@ -1056,13 +1079,13 @@ uint8_t get_max_tpmi(const NR_PUSCH_Config_t *pusch_Config,
   return max_tpmi;
 }
 
-void get_precoder_matrix_coef(char *w,
-                              const uint8_t ul_ri,
-                              const uint16_t num_ue_srs_ports,
-                              const long transform_precoding,
-                              const uint8_t tpmi,
-                              const uint8_t uI,
-                              int layer_idx)
+static void get_precoder_matrix_coef(char *w,
+                                     const uint8_t ul_ri,
+                                     const uint16_t num_ue_srs_ports,
+                                     const long transform_precoding,
+                                     const uint8_t tpmi,
+                                     const uint8_t uI,
+                                     int layer_idx)
 {
   if (ul_ri == 0) {
     if (num_ue_srs_ports == 2) {
@@ -1085,15 +1108,16 @@ void get_precoder_matrix_coef(char *w,
   }
 }
 
-int nr_srs_tpmi_estimation(const NR_PUSCH_Config_t *pusch_Config,
-                           const long transform_precoding,
-                           const uint8_t *channel_matrix,
-                           const uint8_t normalized_iq_representation,
-                           const uint16_t num_gnb_antenna_elements,
-                           const uint16_t num_ue_srs_ports,
-                           const uint16_t prg_size,
-                           const uint16_t num_prgs,
-                           const uint8_t ul_ri) {
+static int nr_srs_tpmi_estimation(const NR_PUSCH_Config_t *pusch_Config,
+                                  const long transform_precoding,
+                                  const uint8_t *channel_matrix,
+                                  const uint8_t normalized_iq_representation,
+                                  const uint16_t num_gnb_antenna_elements,
+                                  const uint16_t num_ue_srs_ports,
+                                  const uint16_t prg_size,
+                                  const uint16_t num_prgs,
+                                  const uint8_t ul_ri)
+{
   if (ul_ri > 1) {
     LOG_D(NR_MAC, "TPMI computation for ul_ri %i is not implemented yet!\n", ul_ri);
     return 0;
@@ -1173,6 +1197,8 @@ void handle_nr_srs_measurements(const module_id_t module_id,
                                 const sub_frame_t slot,
                                 nfapi_nr_srs_indication_pdu_t *srs_ind)
 {
+  gNB_MAC_INST *nrmac = RC.nrmac[module_id];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
   LOG_D(NR_MAC, "(%d.%d) Received SRS indication for UE %04x\n", frame, slot, srs_ind->rnti);
 
 #ifdef SRS_IND_DEBUG
@@ -1188,11 +1214,13 @@ void handle_nr_srs_measurements(const module_id_t module_id,
   NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[module_id]->UE_info, srs_ind->rnti);
   if (!UE) {
     LOG_W(NR_MAC, "Could not find UE for RNTI %04x\n", srs_ind->rnti);
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     return;
   }
 
   if (srs_ind->timing_advance_offset == 0xFFFF) {
     LOG_W(NR_MAC, "Invalid timing advance offset for RNTI %04x\n", srs_ind->rnti);
+    NR_SCHED_UNLOCK(&nrmac->sched_lock);
     return;
   }
 
@@ -1210,6 +1238,7 @@ void handle_nr_srs_measurements(const module_id_t module_id,
 
       if (nr_srs_bf_report.wide_band_snr == 0xFF) {
         LOG_W(NR_MAC, "Invalid wide_band_snr for RNTI %04x\n", srs_ind->rnti);
+        NR_SCHED_UNLOCK(&nrmac->sched_lock);
         return;
       }
 
@@ -1308,12 +1337,14 @@ void handle_nr_srs_measurements(const module_id_t module_id,
     default:
       AssertFatal(1 == 0, "Invalid SRS usage\n");
   }
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 long get_K2(NR_PUSCH_TimeDomainResourceAllocationList_t *tdaList,
             int time_domain_assignment,
             int mu) {
 
+  /* we assume that this function is mutex-protected from outside */
   NR_PUSCH_TimeDomainResourceAllocation_t *tda = tdaList->list.array[time_domain_assignment];
 
   if (tda->k2)
@@ -1363,7 +1394,7 @@ static bool nr_UE_is_to_be_scheduled(const NR_ServingCellConfigCommon_t *scc, in
   return has_data || sched_ctrl->SR || high_inactivity;
 }
 
-void update_ul_ue_R_Qm(int mcs, int mcs_table, const NR_PUSCH_Config_t *pusch_Config, uint16_t *R, uint8_t *Qm)
+static void update_ul_ue_R_Qm(int mcs, int mcs_table, const NR_PUSCH_Config_t *pusch_Config, uint16_t *R, uint8_t *Qm)
 {
   *R = nr_get_code_rate_ul(mcs, mcs_table);
   *Qm = nr_get_Qm_ul(mcs, mcs_table);
@@ -1374,7 +1405,14 @@ void update_ul_ue_R_Qm(int mcs, int mcs_table, const NR_PUSCH_Config_t *pusch_Co
   }
 }
 
-void nr_ue_max_mcs_min_rb(int mu, int ph_limit, NR_sched_pusch_t *sched_pusch, NR_UE_UL_BWP_t *ul_bwp, uint16_t minRb, uint32_t tbs, uint16_t *Rb, uint8_t *mcs)
+static void nr_ue_max_mcs_min_rb(int mu,
+                                 int ph_limit,
+                                 NR_sched_pusch_t *sched_pusch,
+                                 NR_UE_UL_BWP_t *ul_bwp,
+                                 uint16_t minRb,
+                                 uint32_t tbs,
+                                 uint16_t *Rb,
+                                 uint8_t *mcs)
 {
   AssertFatal(*Rb >= minRb, "illegal Rb %d < minRb %d\n", *Rb, minRb);
   AssertFatal(*mcs >= 0 && *mcs <= 28, "illegal MCS %d\n", *mcs);
@@ -1577,13 +1615,13 @@ static int comparator(const void *p, const void *q) {
   return ((UEsched_t*)p)->coef < ((UEsched_t*)q)->coef;
 }
 
-void pf_ul(module_id_t module_id,
-           frame_t frame,
-           sub_frame_t slot,
-           NR_UE_info_t *UE_list[],
-           int max_num_ue,
-           int n_rb_sched,
-           uint16_t *rballoc_mask)
+static void pf_ul(module_id_t module_id,
+                  frame_t frame,
+                  sub_frame_t slot,
+                  NR_UE_info_t *UE_list[],
+                  int max_num_ue,
+                  int n_rb_sched,
+                  uint16_t *rballoc_mask)
 {
 
   const int CC_id = 0;
@@ -1870,7 +1908,7 @@ void pf_ul(module_id_t module_id,
   }
 }
 
-bool nr_fr1_ulsch_preprocessor(module_id_t module_id, frame_t frame, sub_frame_t slot)
+static bool nr_fr1_ulsch_preprocessor(module_id_t module_id, frame_t frame, sub_frame_t slot)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_id];
   NR_COMMON_channels_t *cc = nr_mac->common_channels;
@@ -1971,6 +2009,7 @@ bool nr_fr1_ulsch_preprocessor(module_id_t module_id, frame_t frame, sub_frame_t
 
 nr_pp_impl_ul nr_init_fr1_ulsch_preprocessor(int CC_id)
 {
+  /* during initialization: no mutex needed */
   /* in the PF algorithm, we have to use the TBsize to compute the coefficient.
    * This would include the number of DMRS symbols, which in turn depends on
    * the time domain allocation. In case we are in a mixed slot, we do not want
@@ -2002,6 +2041,9 @@ nr_pp_impl_ul nr_init_fr1_ulsch_preprocessor(int CC_id)
 void nr_schedule_ulsch(module_id_t module_id, frame_t frame, sub_frame_t slot, nfapi_nr_ul_dci_request_t *ul_dci_req)
 {
   gNB_MAC_INST *nr_mac = RC.nrmac[module_id];
+  /* already mutex protected: held in gNB_dlsch_ulsch_scheduler() */
+  NR_SCHED_ENSURE_LOCKED(&nr_mac->sched_lock);
+
   /* Uplink data ONLY can be scheduled when the current slot is downlink slot,
    * because we have to schedule the DCI0 first before schedule uplink data */
   if (!is_xlsch_in_slot(nr_mac->dlsch_slot_bitmap[slot / 64], slot)) {
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
index a700c5c652b003f431c8da0335234170c42c03c6..c2cbe8799dc2c501b302ebeccddcd29f3c5cb8a7 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
@@ -40,8 +40,6 @@ void set_cset_offset(uint16_t);
 
 void mac_top_init_gNB(ngran_node_t node_type);
 
-void config_common(gNB_MAC_INST *nrmac, int pdsch_AntennaPorts, int pusch_AntennaPorts, NR_ServingCellConfigCommon_t *scc);
-
 int nr_mac_enable_ue_rrc_processing_timer(module_id_t Mod_idP,
                                           rnti_t rnti,
                                           NR_SubcarrierSpacing_t subcarrierSpacing,
@@ -58,7 +56,6 @@ void nr_mac_config_sib1(gNB_MAC_INST *nrmac, NR_BCCH_DL_SCH_Message_t *sib1);
 bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup);
 bool nr_mac_prepare_ra_nsa_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup);
 bool nr_mac_update_cellgroup(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup);
-bool nr_mac_update_RA(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t *CellGroup);
 
 void clear_nr_nfapi_information(gNB_MAC_INST *gNB,
                                 int CC_idP,
@@ -87,15 +84,6 @@ void nr_schedule_ue_spec(module_id_t module_id,
                          nfapi_nr_dl_tti_request_t *DL_req,
                          nfapi_nr_tx_data_request_t *TX_req);
 
-uint32_t schedule_control_sib1(module_id_t module_id,
-                               int CC_id,
-                               NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config,
-                               int time_domain_allocation,
-                               NR_pdsch_dmrs_t *dmrs_parms,
-                               NR_tda_info_t *tda_info,
-                               uint8_t candidate_idx,
-                               uint16_t num_total_bytes);
-
 /* \brief default FR1 DL preprocessor init routine, returns preprocessor to call */
 nr_pp_impl_dl nr_init_fr1_dlsch_preprocessor(int CC_id);
 
@@ -139,41 +127,6 @@ void nr_clear_ra_proc(module_id_t module_idP, int CC_id, frame_t frameP, NR_RA_t
 
 int nr_allocate_CCEs(int module_idP, int CC_idP, frame_t frameP, sub_frame_t slotP, int test_only);
 
-void nr_get_Msg3alloc(module_id_t module_id,
-                      int CC_id,
-                      NR_ServingCellConfigCommon_t *scc,
-                      sub_frame_t current_subframe,
-                      frame_t current_frame,
-                      NR_RA_t *ra,
-                      int16_t *tdd_beam_association);
-
-void nr_generate_Msg3_retransmission(module_id_t module_idP,
-                                     int CC_id,
-                                     frame_t frameP,
-                                     sub_frame_t slotP,
-                                     NR_RA_t *ra,
-                                     nfapi_nr_ul_dci_request_t *ul_dci_req);
-
-/* \brief Function in gNB to fill RAR pdu when requested by PHY.
-@param ra Instance of RA resources of gNB
-@param dlsch_buffer Pointer to RAR input buffer
-@param N_RB_UL Number of UL resource blocks
-*/
-void nr_fill_rar(uint8_t Mod_idP,
-                 NR_RA_t * ra,
-                 uint8_t * dlsch_buffer,
-                 nfapi_nr_pusch_pdu_t  *pusch_pdu);
-
-void fill_msg3_pusch_pdu(nfapi_nr_pusch_pdu_t *pusch_pdu,
-                         NR_ServingCellConfigCommon_t *scc,
-                         int round,
-                         int startSymbolAndLength,
-                         rnti_t rnti, int scs,
-                         int bwp_size, int bwp_start,
-                         int mappingtype, int fh,
-                         int msg3_first_rb, int msg3_nb_rb);
-
-
 void schedule_nr_prach(module_id_t module_idP, frame_t frameP, sub_frame_t slotP);
 
 uint16_t nr_mac_compute_RIV(uint16_t N_RB_DL, uint16_t RBstart, uint16_t Lcrbs);
@@ -380,39 +333,10 @@ int nr_write_ce_dlsch_pdu(module_id_t module_idP,
                           unsigned char drx_cmd,
                           unsigned char *ue_cont_res_id);
 
-void nr_generate_Msg2(module_id_t module_idP,
-                      int CC_id,
-                      frame_t frameP,
-                      sub_frame_t slotP,
-                      NR_RA_t *ra,
-                      nfapi_nr_dl_tti_request_t *dl_req,
-                      nfapi_nr_tx_data_request_t *TX_req);
-
-void nr_generate_Msg4(module_id_t module_idP,
-                      int CC_id,
-                      frame_t frameP,
-                      sub_frame_t slotP,
-                      NR_RA_t *ra,
-                      nfapi_nr_dl_tti_request_t *DL_req,
-                      nfapi_nr_tx_data_request_t *TX_req);
-
-void nr_check_Msg4_Ack(module_id_t module_id, int CC_id, frame_t frame, sub_frame_t slot, NR_RA_t *ra);
-
-void nr_generate_Msg3_dcch_dtch_response(module_id_t module_idP,
-                                         int CC_id,
-                                         frame_t frameP,
-                                         sub_frame_t slotP,
-                                         NR_RA_t *ra,
-                                         nfapi_nr_dl_tti_request_t *DL_req,
-                                         nfapi_nr_tx_data_request_t *TX_req);
-
 int binomial(int n, int k);
 
 bool is_xlsch_in_slot(uint64_t bitmap, sub_frame_t slot);
 
-void fill_ssb_vrb_map (NR_COMMON_channels_t *cc, int rbStart, int ssb_subcarrier_offset, uint16_t symStart, int CC_id);
-
-
 /* \brief Function to indicate a received SDU on ULSCH.
 @param Mod_id Instance ID of gNB
 @param CC_id Component carrier index
@@ -451,13 +375,6 @@ void handle_nr_srs_measurements(const module_id_t module_id,
                                 const sub_frame_t slot,
                                 nfapi_nr_srs_indication_pdu_t *srs_ind);
 
-int16_t ssb_index_from_prach(module_id_t module_idP,
-                             frame_t frameP,
-                             sub_frame_t slotP,
-                             uint16_t preamble_index,
-                             uint8_t freq_index,
-                             uint8_t symbol);
-
 void find_SSB_and_RO_available(gNB_MAC_INST *nrmac);
 
 NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c
index 0a47699591de0670766db89972f695578a541e14..4fdad6fb8151072b9d71a7b4d4d30e473bfea49e 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c
@@ -22,12 +22,130 @@
 #include "mac_rrc_dl_handler.h"
 
 #include "mac_proto.h"
-#include "openair2/RRC/NR/rrc_gNB_UE_context.h"
 #include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
+#include "openair2/RRC/NR/MESSAGES/asn1_msg.h"
 
-#include "NR_RRCSetup.h"
-#include "NR_DL-CCCH-Message.h"
-#include "NR_CellGroupConfig.h"
+static NR_RLC_BearerConfig_t *get_bearerconfig_from_srb(const f1ap_srb_to_be_setup_t *srb)
+{
+  long priority = srb->srb_id; // high priority for SRB
+  e_NR_LogicalChannelConfig__ul_SpecificParameters__bucketSizeDuration bucket =
+      NR_LogicalChannelConfig__ul_SpecificParameters__bucketSizeDuration_ms5;
+  return get_SRB_RLC_BearerConfig(srb->srb_id, priority, bucket);
+}
+
+static void handle_ue_context_srbs_setup(const f1ap_ue_context_setup_t *req,
+                                         f1ap_ue_context_setup_t *resp,
+                                         NR_CellGroupConfig_t *cellGroupConfig)
+{
+  DevAssert(req != NULL && resp != NULL && cellGroupConfig != NULL);
+
+  resp->srbs_to_be_setup_length = req->srbs_to_be_setup_length;
+  resp->srbs_to_be_setup = calloc(req->srbs_to_be_setup_length, sizeof(*resp->srbs_to_be_setup));
+  AssertFatal(resp->srbs_to_be_setup != NULL, "out of memory\n");
+  for (int i = 0; i < req->srbs_to_be_setup_length; i++) {
+    f1ap_srb_to_be_setup_t *srb = &req->srbs_to_be_setup[i];
+    NR_RLC_BearerConfig_t *rlc_BearerConfig = get_bearerconfig_from_srb(srb);
+    nr_rlc_add_srb(req->rnti, srb->srb_id, rlc_BearerConfig);
+
+    resp->srbs_to_be_setup[i] = *srb;
+
+    int ret = ASN_SEQUENCE_ADD(&cellGroupConfig->rlc_BearerToAddModList->list, rlc_BearerConfig);
+    DevAssert(ret == 0);
+  }
+}
+
+static NR_RLC_BearerConfig_t *get_bearerconfig_from_drb(const f1ap_drb_to_be_setup_t *drb)
+{
+  const NR_RLC_Config_PR rlc_conf = drb->rlc_mode == RLC_MODE_UM ? NR_RLC_Config_PR_um_Bi_Directional : NR_RLC_Config_PR_am;
+  long priority = 13; // hardcoded for the moment
+  return get_DRB_RLC_BearerConfig(3 + drb->drb_id, drb->drb_id, rlc_conf, priority);
+}
+
+static void handle_ue_context_drbs_setup(const f1ap_ue_context_setup_t *req,
+                                         f1ap_ue_context_setup_t *resp,
+                                         NR_CellGroupConfig_t *cellGroupConfig)
+{
+  DevAssert(req != NULL && resp != NULL && cellGroupConfig != NULL);
+
+  /* Note: the actual GTP tunnels are created in the F1AP breanch of
+   * ue_context_*_response() */
+  resp->drbs_to_be_setup_length = req->drbs_to_be_setup_length;
+  resp->drbs_to_be_setup = calloc(req->drbs_to_be_setup_length, sizeof(*resp->drbs_to_be_setup));
+  AssertFatal(resp->drbs_to_be_setup != NULL, "out of memory\n");
+  for (int i = 0; i < req->drbs_to_be_setup_length; i++) {
+    f1ap_drb_to_be_setup_t *drb = &req->drbs_to_be_setup[i];
+    NR_RLC_BearerConfig_t *rlc_BearerConfig = get_bearerconfig_from_drb(drb);
+    nr_rlc_add_drb(req->rnti, drb->drb_id, rlc_BearerConfig);
+
+    resp->drbs_to_be_setup[i] = *drb;
+
+    int ret = ASN_SEQUENCE_ADD(&cellGroupConfig->rlc_BearerToAddModList->list, rlc_BearerConfig);
+    DevAssert(ret == 0);
+  }
+}
+
+void ue_context_setup_request(const f1ap_ue_context_setup_t *req)
+{
+  gNB_MAC_INST *mac = RC.nrmac[0];
+  /* response has same type as request... */
+  f1ap_ue_context_setup_t resp = {
+    .gNB_CU_ue_id = req->gNB_CU_ue_id,
+    .gNB_DU_ue_id = req->gNB_DU_ue_id,
+    .rnti = req->rnti,
+  };
+
+  if (req->cu_to_du_rrc_information != NULL) {
+    AssertFatal(req->cu_to_du_rrc_information->cG_ConfigInfo == NULL, "CG-ConfigInfo not handled\n");
+    AssertFatal(req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList == NULL, "UE capabilities not handled yet\n");
+    AssertFatal(req->cu_to_du_rrc_information->measConfig == NULL, "MeasConfig not handled\n");
+  }
+
+  NR_SCHED_LOCK(&mac->sched_lock);
+
+  NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[0]->UE_info, req->rnti);
+  AssertFatal(UE != NULL, "did not find UE with RNTI %04x, but UE Context Setup Failed not implemented\n", UE->rnti);
+
+  if (req->srbs_to_be_setup_length > 0)
+    handle_ue_context_srbs_setup(req, &resp, UE->CellGroup);
+
+  if (req->drbs_to_be_setup_length > 0) {
+    handle_ue_context_drbs_setup(req, &resp, NULL);
+  }
+
+  if (req->rrc_container != NULL)
+    nr_rlc_srb_recv_sdu(req->rnti, DCCH, req->rrc_container, req->rrc_container_length);
+
+  //nr_mac_update_cellgroup()
+  resp.du_to_cu_rrc_information = calloc(1, sizeof(du_to_cu_rrc_information_t));
+  AssertFatal(resp.du_to_cu_rrc_information != NULL, "out of memory\n");
+  resp.du_to_cu_rrc_information->cellGroupConfig = calloc(1,1024);
+  AssertFatal(resp.du_to_cu_rrc_information->cellGroupConfig != NULL, "out of memory\n");
+  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig,
+                                                  NULL,
+                                                  UE->CellGroup,
+                                                  resp.du_to_cu_rrc_information->cellGroupConfig,
+                                                  1024);
+  AssertFatal(enc_rval.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval.failed_type->name);
+  resp.du_to_cu_rrc_information->cellGroupConfig_length = (enc_rval.encoded + 7) >> 3;
+
+  /* TODO: need to apply after UE context reconfiguration confirmed? */
+  process_CellGroup(UE->CellGroup, &UE->UE_sched_ctrl);
+
+  NR_SCHED_UNLOCK(&mac->sched_lock);
+
+  /* some sanity checks, since we use the same type for request and response */
+  DevAssert(resp.cu_to_du_rrc_information == NULL);
+  DevAssert(resp.du_to_cu_rrc_information != NULL);
+  DevAssert(resp.rrc_container == NULL && resp.rrc_container_length == 0);
+
+  mac->mac_rrc.ue_context_setup_response(req, &resp);
+
+  /* free the memory we allocated above */
+  free(resp.srbs_to_be_setup);
+  free(resp.drbs_to_be_setup);
+  free(resp.du_to_cu_rrc_information->cellGroupConfig);
+  free(resp.du_to_cu_rrc_information);
+}
 
 int dl_rrc_message(module_id_t module_id, const f1ap_dl_rrc_message_t *dl_rrc)
 {
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h
index ecbc95eef07190ea4fee1976f2a953c4e3040894..25b51c2d845924eda11e211943f71d66fd894c90 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h
@@ -25,6 +25,8 @@
 #include "platform_types.h"
 #include "f1ap_messages_types.h"
 
+void ue_context_setup_request(const f1ap_ue_context_setup_t *req);
+
 int dl_rrc_message(module_id_t module_id, const f1ap_dl_rrc_message_t *dl_rrc);
 
 #endif /* MAC_RRC_DL_HANDLER_H */
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul.h b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul.h
index cb80ba4fa55c1f5c3a6ba9df5e6a41b546a3a850..53736a1695145c0a7f5c1b6f870712251e86dfb4 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul.h
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul.h
@@ -25,6 +25,8 @@
 #include "platform_types.h"
 #include "f1ap_messages_types.h"
 
+typedef void (*ue_context_setup_response_func_t)(const f1ap_ue_context_setup_t* req, const f1ap_ue_context_setup_t *resp);
+
 typedef void (*initial_ul_rrc_message_transfer_func_t)(module_id_t module_id, const f1ap_initial_ul_rrc_message_t *ul_rrc);
 
 struct nr_mac_rrc_ul_if_s;
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c
index ace8b17f4912d7e14f7dacc719fc4d5b1e4aaab0..eafa54418567f30d65fa0e30a4d39890521ceb17 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c
@@ -24,6 +24,37 @@
 
 #include "mac_rrc_ul.h"
 
+static void ue_context_setup_response_direct(const f1ap_ue_context_setup_t *req, const f1ap_ue_context_setup_t *resp)
+{
+  DevAssert(req->drbs_to_be_setup_length == resp->drbs_to_be_setup_length);
+  AssertFatal(req->drbs_to_be_setup_length == 0, "not implemented\n");
+
+  (void) req; /* we don't need the request -- it is to set up GTP in F1 case */
+  MessageDef *msg = itti_alloc_new_message (TASK_MAC_GNB, 0, F1AP_UE_CONTEXT_SETUP_RESP);
+  f1ap_ue_context_setup_t *f1ap_msg = &F1AP_UE_CONTEXT_SETUP_RESP(msg);
+  /* copy all fields, but reallocate memory buffers! */
+  *f1ap_msg = *resp;
+
+  if (resp->srbs_to_be_setup_length > 0) {
+    DevAssert(resp->srbs_to_be_setup != NULL);
+    f1ap_msg->srbs_to_be_setup_length = resp->srbs_to_be_setup_length;
+    f1ap_msg->srbs_to_be_setup = calloc(f1ap_msg->srbs_to_be_setup_length, sizeof(*f1ap_msg->srbs_to_be_setup));
+    for (int i = 0; i < f1ap_msg->srbs_to_be_setup_length; ++i)
+      f1ap_msg->srbs_to_be_setup[i] = resp->srbs_to_be_setup[i];
+  }
+
+  f1ap_msg->du_to_cu_rrc_information = malloc(sizeof(*resp->du_to_cu_rrc_information));
+  AssertFatal(f1ap_msg->du_to_cu_rrc_information != NULL, "out of memory\n");
+  f1ap_msg->du_to_cu_rrc_information_length = resp->du_to_cu_rrc_information_length;
+  du_to_cu_rrc_information_t *du2cu = f1ap_msg->du_to_cu_rrc_information;
+  du2cu->cellGroupConfig_length = resp->du_to_cu_rrc_information->cellGroupConfig_length;
+  du2cu->cellGroupConfig = calloc(du2cu->cellGroupConfig_length, sizeof(*du2cu->cellGroupConfig));
+  AssertFatal(du2cu->cellGroupConfig != NULL, "out of memory\n");
+  memcpy(du2cu->cellGroupConfig, resp->du_to_cu_rrc_information->cellGroupConfig, du2cu->cellGroupConfig_length);
+
+  itti_send_msg_to_task(TASK_RRC_GNB, 0, msg);
+}
+
 static void initial_ul_rrc_message_transfer_direct(module_id_t module_id, const f1ap_initial_ul_rrc_message_t *ul_rrc)
 {
   MessageDef *msg = itti_alloc_new_message(TASK_MAC_GNB, 0, F1AP_INITIAL_UL_RRC_MESSAGE);
@@ -46,5 +77,6 @@ static void initial_ul_rrc_message_transfer_direct(module_id_t module_id, const
 
 void mac_rrc_ul_direct_init(struct nr_mac_rrc_ul_if_s *mac_rrc)
 {
+  mac_rrc->ue_context_setup_response = ue_context_setup_response_direct;
   mac_rrc->initial_ul_rrc_message_transfer = initial_ul_rrc_message_transfer_direct;
 }
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c
index 13547c5291ed0314e6cdaffa3de5a0409a45d660..c5bcb8814a4ffc936844f32f0961952421707f89 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c
@@ -19,11 +19,47 @@
  *      conmnc_digit_lengtht@openairinterface.org
  */
 
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
 #include "nr_mac_gNB.h"
 #include "intertask_interface.h"
+#include "openair3/ocp-gtpu/gtp_itf.h"
 
 #include "mac_rrc_ul.h"
 
+static void ue_context_setup_response_f1ap(const f1ap_ue_context_setup_t *req, const f1ap_ue_context_setup_t *resp)
+{
+  DevAssert(req->drbs_to_be_setup_length == resp->drbs_to_be_setup_length);
+  AssertFatal(req->drbs_to_be_setup_length == 0, "not implmented\n");
+
+  DevAssert(req->srbs_to_be_setup_length == resp->srbs_to_be_setup_length);
+  MessageDef *msg = itti_alloc_new_message (TASK_MAC_GNB, 0, F1AP_UE_CONTEXT_SETUP_RESP);
+  f1ap_ue_context_setup_t *f1ap_msg = &F1AP_UE_CONTEXT_SETUP_RESP(msg);
+  /* copy all fields, but reallocate rrc_containers! */
+  *f1ap_msg = *resp;
+
+  if (resp->srbs_to_be_setup_length > 0) {
+    DevAssert(resp->srbs_to_be_setup != NULL);
+    f1ap_msg->srbs_to_be_setup_length = resp->srbs_to_be_setup_length;
+    f1ap_msg->srbs_to_be_setup = calloc(f1ap_msg->srbs_to_be_setup_length, sizeof(*f1ap_msg->srbs_to_be_setup));
+    for (int i = 0; i < f1ap_msg->srbs_to_be_setup_length; ++i)
+      f1ap_msg->srbs_to_be_setup[i] = resp->srbs_to_be_setup[i];
+  }
+
+  f1ap_msg->du_to_cu_rrc_information = malloc(sizeof(*resp->du_to_cu_rrc_information));
+  AssertFatal(f1ap_msg->du_to_cu_rrc_information != NULL, "out of memory\n");
+  f1ap_msg->du_to_cu_rrc_information_length = resp->du_to_cu_rrc_information_length;
+  du_to_cu_rrc_information_t *du2cu = f1ap_msg->du_to_cu_rrc_information;
+  du2cu->cellGroupConfig_length = resp->du_to_cu_rrc_information->cellGroupConfig_length;
+  du2cu->cellGroupConfig = calloc(du2cu->cellGroupConfig_length, sizeof(*du2cu->cellGroupConfig));
+  AssertFatal(du2cu->cellGroupConfig != NULL, "out of memory\n");
+  memcpy(du2cu->cellGroupConfig, resp->du_to_cu_rrc_information->cellGroupConfig, du2cu->cellGroupConfig_length);
+
+  itti_send_msg_to_task(TASK_DU_F1, 0, msg);
+}
+
 static void initial_ul_rrc_message_transfer_f1ap(module_id_t module_id, const f1ap_initial_ul_rrc_message_t *ul_rrc)
 {
   MessageDef *msg = itti_alloc_new_message(TASK_MAC_GNB, 0, F1AP_INITIAL_UL_RRC_MESSAGE);
@@ -46,6 +82,7 @@ static void initial_ul_rrc_message_transfer_f1ap(module_id_t module_id, const f1
 
 void mac_rrc_ul_f1ap_init(struct nr_mac_rrc_ul_if_s *mac_rrc)
 {
+  mac_rrc->ue_context_setup_response = ue_context_setup_response_f1ap;
   mac_rrc->initial_ul_rrc_message_transfer = initial_ul_rrc_message_transfer_f1ap;
 }
 
diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c
index cd8e16c48b9ead19a33ae62d6a9bdfa8b440df48..d7cd8e1b4babfd95fe84a54cfc8cc6d753add63e 100644
--- a/openair2/LAYER2/NR_MAC_gNB/main.c
+++ b/openair2/LAYER2/NR_MAC_gNB/main.c
@@ -60,7 +60,9 @@ void *nrmac_stats_thread(void *arg) {
 
   while (oai_exit == 0) {
     char *p = output;
+    NR_SCHED_LOCK(&gNB->sched_lock);
     p += dump_mac_stats(gNB, p, end - p, false);
+    NR_SCHED_UNLOCK(&gNB->sched_lock);
     p += snprintf(p, end - p, "\n");
     p += print_meas_log(&gNB->eNB_scheduler, "DL & UL scheduling timing", NULL, NULL, p, end - p);
     p += print_meas_log(&gNB->schedule_dlsch, "dlsch scheduler", NULL, NULL, p, end - p);
@@ -87,7 +89,11 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset
   const char *begin = output;
   const char *end = output + strlen;
 
-  pthread_mutex_lock(&gNB->UE_info.mutex);
+  /* this function is called from gNB_dlsch_ulsch_scheduler(), so assumes the
+   * scheduler to be locked*/
+  NR_SCHED_ENSURE_LOCKED(&gNB->sched_lock);
+
+  NR_SCHED_LOCK(&gNB->UE_info.mutex);
   UE_iterator(gNB->UE_info.list, UE) {
     NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
     NR_mac_stats_t *stats = &UE->mac_stats;
@@ -175,7 +181,7 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset
                            stats->ul.lc_bytes[lc_id]);
     }
   }
-  pthread_mutex_unlock(&gNB->UE_info.mutex);
+  NR_SCHED_UNLOCK(&gNB->UE_info.mutex);
   return output - begin;
 }
 
@@ -233,6 +239,8 @@ void mac_top_init_gNB(ngran_node_t node_type)
 
       RC.nrmac[i]->first_MIB = true;
 
+      pthread_mutex_init(&RC.nrmac[i]->sched_lock, NULL);
+
       pthread_mutex_init(&RC.nrmac[i]->UE_info.mutex, NULL);
       uid_linear_allocator_init(&RC.nrmac[i]->UE_info.uid_allocator);
 
diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
index b8517da55e44214125234ed59e953de6ee4b8702..5f25713650a7ec1a3f8fcdf49c2c2b29d8e36b1b 100644
--- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
+++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
@@ -41,6 +41,25 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <pthread.h>
+
+#define NR_SCHED_LOCK(lock)                                        \
+  do {                                                             \
+    int rc = pthread_mutex_lock(lock);                             \
+    AssertFatal(rc == 0, "error while locking scheduler mutex\n"); \
+  } while (0)
+
+#define NR_SCHED_UNLOCK(lock)                                      \
+  do {                                                             \
+    int rc = pthread_mutex_unlock(lock);                           \
+    AssertFatal(rc == 0, "error while locking scheduler mutex\n"); \
+  } while (0)
+
+#define NR_SCHED_ENSURE_LOCKED(lock)\
+  do {\
+    int rc = pthread_mutex_trylock(lock); \
+    AssertFatal(rc == EBUSY, "this function should be called with the scheduler mutex locked\n");\
+  } while (0)
 
 /* Commmon */
 #include "radio/COMMON/common_lib.h"
@@ -641,6 +660,7 @@ typedef struct NR_bler_options {
 
 typedef struct nr_mac_rrc_ul_if_s {
   /* TODO add other message types as necessary */
+  ue_context_setup_response_func_t ue_context_setup_response;
   initial_ul_rrc_message_transfer_func_t initial_ul_rrc_message_transfer;
 } nr_mac_rrc_ul_if_t;
 
@@ -795,6 +815,8 @@ typedef struct gNB_MAC_INST_s {
   int16_t frame;
   int16_t slot;
 
+  pthread_mutex_t sched_lock;
+
 } gNB_MAC_INST;
 
 #endif /*__LAYER2_NR_MAC_GNB_H__ */
diff --git a/openair2/RRC/NR/L2_nr_interface.c b/openair2/RRC/NR/L2_nr_interface.c
index 2e83c69af28f7ed6fd499e0c124ae9d02c7b0fed..4e9c077afaa1d4a8ea951afe528e4bf3399d022e 100644
--- a/openair2/RRC/NR/L2_nr_interface.c
+++ b/openair2/RRC/NR/L2_nr_interface.c
@@ -55,12 +55,17 @@ void nr_rrc_mac_remove_ue(rnti_t rntiMaybeUEid)
   nr_rlc_remove_ue(rntiMaybeUEid);
 
   gNB_MAC_INST *nrmac = RC.nrmac[0];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
   mac_remove_nr_ue(nrmac, rntiMaybeUEid);
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 void nr_rrc_mac_update_cellgroup(rnti_t rntiMaybeUEid, NR_CellGroupConfig_t *cgc)
 {
-  nr_mac_update_cellgroup(RC.nrmac[0], rntiMaybeUEid, cgc);
+  gNB_MAC_INST *nrmac = RC.nrmac[0];
+  NR_SCHED_LOCK(&nrmac->sched_lock);
+  nr_mac_update_cellgroup(nrmac, rntiMaybeUEid, cgc);
+  NR_SCHED_UNLOCK(&nrmac->sched_lock);
 }
 
 uint16_t mac_rrc_nr_data_req(const module_id_t Mod_idP,
diff --git a/openair2/RRC/NR/mac_rrc_dl.h b/openair2/RRC/NR/mac_rrc_dl.h
index 1943490cd6accb206de4cda7a66ab2c9f2409079..49ffccd4a7e6ba1a2d16b905bf65a50493f89b84 100644
--- a/openair2/RRC/NR/mac_rrc_dl.h
+++ b/openair2/RRC/NR/mac_rrc_dl.h
@@ -25,6 +25,8 @@
 #include "platform_types.h"
 #include "f1ap_messages_types.h"
 
+typedef void (*ue_context_setup_request_func_t)(const f1ap_ue_context_setup_t *req);
+
 typedef void (*dl_rrc_message_transfer_func_t)(module_id_t module_id, const f1ap_dl_rrc_message_t *dl_rrc);
 
 struct nr_mac_rrc_dl_if_s;
diff --git a/openair2/RRC/NR/mac_rrc_dl_direct.c b/openair2/RRC/NR/mac_rrc_dl_direct.c
index 5c8d9d9d5bd1b759a8a0e3997986afc695539b8c..be416d69215a9c4002603a2cc5187f1a17d9f0eb 100644
--- a/openair2/RRC/NR/mac_rrc_dl_direct.c
+++ b/openair2/RRC/NR/mac_rrc_dl_direct.c
@@ -33,5 +33,6 @@ static void dl_rrc_message_transfer_direct(module_id_t module_id, const f1ap_dl_
 
 void mac_rrc_dl_direct_init(nr_mac_rrc_dl_if_t *mac_rrc)
 {
+  mac_rrc->ue_context_setup_request = ue_context_setup_request;
   mac_rrc->dl_rrc_message_transfer = dl_rrc_message_transfer_direct;
 }
diff --git a/openair2/RRC/NR/mac_rrc_dl_f1ap.c b/openair2/RRC/NR/mac_rrc_dl_f1ap.c
index 40f3b4b6eaa70bc170ce3c112c854b7bb5ba3327..9bbddaf4768e568898ab484f661a07c57541b7fb 100644
--- a/openair2/RRC/NR/mac_rrc_dl_f1ap.c
+++ b/openair2/RRC/NR/mac_rrc_dl_f1ap.c
@@ -19,9 +19,27 @@
  *      conmnc_digit_lengtht@openairinterface.org
  */
 
+#include <stdlib.h>
+
 #include "mac_rrc_dl.h"
 #include "nr_rrc_defs.h"
 
+static void ue_context_setup_request_f1ap(const f1ap_ue_context_setup_t *req)
+{
+  MessageDef *msg = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_UE_CONTEXT_SETUP_REQ);
+  f1ap_ue_context_setup_t *f1ap_msg = &F1AP_UE_CONTEXT_SETUP_REQ(msg);
+  *f1ap_msg = *req;
+  AssertFatal(req->cu_to_du_rrc_information == NULL, "cu_to_du_rrc_information not supported yet\n");
+  f1ap_msg->cu_to_du_rrc_information = malloc(sizeof(*f1ap_msg->cu_to_du_rrc_information));
+  if (req->rrc_container_length > 0) {
+    f1ap_msg->rrc_container = calloc(req->rrc_container_length, sizeof(*f1ap_msg->rrc_container));
+    AssertFatal(f1ap_msg->rrc_container != NULL, "out of memory\n");
+    f1ap_msg->rrc_container_length = req->rrc_container_length;
+    memcpy(f1ap_msg->rrc_container, req->rrc_container, req->rrc_container_length);
+  }
+  itti_send_msg_to_task(TASK_CU_F1, 0, msg);
+}
+
 static void dl_rrc_message_transfer_f1ap(module_id_t module_id, const f1ap_dl_rrc_message_t *dl_rrc)
 {
   /* TODO call F1AP function directly? no real-time constraint here */
@@ -40,5 +58,6 @@ static void dl_rrc_message_transfer_f1ap(module_id_t module_id, const f1ap_dl_rr
 
 void mac_rrc_dl_f1ap_init(nr_mac_rrc_dl_if_t *mac_rrc)
 {
+  mac_rrc->ue_context_setup_request = ue_context_setup_request_f1ap;
   mac_rrc->dl_rrc_message_transfer = dl_rrc_message_transfer_f1ap;
 }
diff --git a/openair2/RRC/NR/nr_rrc_config.c b/openair2/RRC/NR/nr_rrc_config.c
index c739b174b670e60508aa2a9884a09266860ebebe..20ba902244e309daa2e8b7596bafa2537991e092 100644
--- a/openair2/RRC/NR/nr_rrc_config.c
+++ b/openair2/RRC/NR/nr_rrc_config.c
@@ -2323,6 +2323,8 @@ NR_CellGroupConfig_t *get_initial_cellGroupConfig(int uid,
   return cellGroupConfig;
 }
 
+#include "common/ran_context.h"
+#include "nr_rrc_defs.h"
 void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig,
                             const int uid,
                             NR_UE_NR_Capability_t *uecap,
@@ -2338,6 +2340,13 @@ void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig,
     return;
   DevAssert(configuration->scc != NULL);
 
+  /* This is a hack and will be removed once the CellGroupConfig is fully
+   * handled at the DU */
+  if (NODE_IS_CU(RC.nrrrc[0]->node_type)) {
+    LOG_W(RRC, "update of CellGroupConfig not yet supported in F1\n");
+    return;
+  }
+
   NR_SpCellConfig_t *SpCellConfig = cellGroupConfig->spCellConfig;
   NR_ServingCellConfigCommon_t *scc = configuration->scc;
 
diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h
index 93acdeac7bd94c681b9e959f20066849cfcf49f2..942e9ffb1f1889c9e05b3eb5301c20b63942bfb4 100644
--- a/openair2/RRC/NR/nr_rrc_defs.h
+++ b/openair2/RRC/NR/nr_rrc_defs.h
@@ -481,6 +481,7 @@ typedef struct {
 
 typedef struct nr_mac_rrc_dl_if_s {
   /* TODO add other message types as necessary */
+  ue_context_setup_request_func_t ue_context_setup_request;
   dl_rrc_message_transfer_func_t dl_rrc_message_transfer;
 } nr_mac_rrc_dl_if_t;
 
diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c
index 2d3628f7651301eabf2095ef5d1781430722224f..81a17afab1fcf81ec396838a66ab216790e9a90b 100644
--- a/openair2/RRC/NR/rrc_gNB.c
+++ b/openair2/RRC/NR/rrc_gNB.c
@@ -363,7 +363,6 @@ static void rrc_gNB_generate_RRCSetup(instance_t instance,
   // remove UE after 10 frames after RRCConnectionRelease is triggered
   ue_context_pP->ue_context.ue_release_timer_thres = 1000;
 
-  /* TODO: this should go through the E1 interface */
   nr_pdcp_add_srbs(true, rnti, ue_context_pP->ue_context.SRB_configList, 0, NULL, NULL);
 
   f1ap_dl_rrc_message_t dl_rrc = {
@@ -497,6 +496,7 @@ static void rrc_gNB_generate_defaultRRCReconfiguration(const protocol_ctxt_t *co
 
   /******************** Radio Bearer Config ********************/
   gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context;
+  AssertFatal(ue_p->nb_of_pdusessions == 0, "logic bug: PDU sessions present before RRC Connection established\n");
 
   struct NR_RRCReconfiguration_v1530_IEs__dedicatedNAS_MessageList *dedicatedNAS_MessageList = CALLOC(1, sizeof(*dedicatedNAS_MessageList));
 
@@ -2285,72 +2285,12 @@ rrc_gNB_decode_dcch(
                                     ul_dcch_msg);
       }
 
-      if (!NODE_IS_CU(gnb_rrc_inst->node_type)) {
-        if (UE->established_pdu_sessions_flag == 1) {
-          rrc_gNB_generate_dedicatedRRCReconfiguration(ctxt_pP, ue_context_p, NULL);
-        } else {
-          rrc_gNB_generate_defaultRRCReconfiguration(ctxt_pP, ue_context_p);
-        }
-      } else {
-        /*Generate a UE context setup request message towards the DU to provide the UE
-         *capability info and get the updates on master cell group config from the DU*/
-        MessageDef *message_p;
-        message_p = itti_alloc_new_message (TASK_RRC_GNB, 0, F1AP_UE_CONTEXT_SETUP_REQ);
-        f1ap_ue_context_setup_t *req=&F1AP_UE_CONTEXT_SETUP_REQ (message_p);
-        //UE_IDs will be extracted from F1AP layer
-        req->gNB_CU_ue_id     = 0;
-        req->gNB_DU_ue_id = 0;
-        req->rnti = UE->rnti;
-        req->mcc = gnb_rrc_inst->configuration.mcc[0];
-        req->mnc = gnb_rrc_inst->configuration.mnc[0];
-        req->mnc_digit_length = gnb_rrc_inst->configuration.mnc_digit_length[0];
-        req->nr_cellid = gnb_rrc_inst->nr_cellid;
-
-        if (UE->established_pdu_sessions_flag == 1) {
-          /*Instruction towards the DU for SRB2 configuration*/
-          req->srbs_to_be_setup = malloc(1*sizeof(f1ap_srb_to_be_setup_t));
-          req->srbs_to_be_setup_length = 1;
-          f1ap_srb_to_be_setup_t *SRBs=req->srbs_to_be_setup;
-          SRBs[0].srb_id = 2;
-          SRBs[0].lcid = 2;
-
-          /*Instruction towards the DU for DRB configuration and tunnel creation*/
-          req->drbs_to_be_setup = malloc(1*sizeof(f1ap_drb_to_be_setup_t));
-          req->drbs_to_be_setup_length = 1;
-          f1ap_drb_to_be_setup_t *DRBs=req->drbs_to_be_setup;
-          LOG_I(RRC, "Length of DRB list:%d \n", req->drbs_to_be_setup_length);
-          DRBs[0].drb_id = 1;
-          DRBs[0].rlc_mode = RLC_MODE_AM;
-          DRBs[0].up_ul_tnl[0].tl_address = inet_addr(gnb_rrc_inst->eth_params_s.my_addr);
-          DRBs[0].up_ul_tnl[0].port = gnb_rrc_inst->eth_params_s.my_portd;
-          DRBs[0].up_ul_tnl_length = 1;
-          DRBs[0].up_dl_tnl[0].tl_address = inet_addr(gnb_rrc_inst->eth_params_s.remote_addr);
-          DRBs[0].up_dl_tnl[0].port = gnb_rrc_inst->eth_params_s.remote_portd;
-          DRBs[0].up_dl_tnl_length = 1;
-        }
-        if( ul_dcch_msg->message.choice.c1->choice.ueCapabilityInformation->criticalExtensions.present ==
-                NR_UECapabilityInformation__criticalExtensions_PR_ueCapabilityInformation ) {
-          struct NR_UE_CapabilityRAT_ContainerList  *ue_CapabilityRAT_ContainerList =
-              ul_dcch_msg->message.choice.c1->choice.ueCapabilityInformation->criticalExtensions.choice.ueCapabilityInformation->ue_CapabilityRAT_ContainerList;
-          if(ue_CapabilityRAT_ContainerList!=NULL){
-            LOG_I(NR_RRC, "ue_CapabilityRAT_ContainerList is present \n");
-            req->cu_to_du_rrc_information = calloc(1,sizeof(cu_to_du_rrc_information_t));
-            req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList = calloc(1,4096);
-            asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_UE_CapabilityRAT_ContainerList,
-              NULL,
-              ue_CapabilityRAT_ContainerList,
-              req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList,
-              4096);
-            AssertFatal (enc_rval.encoded > 0, "ASN1 ue_CapabilityRAT_ContainerList encoding failed (%s, %jd)!\n",
-                             enc_rval.failed_type->name, enc_rval.encoded);
-            req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length = (enc_rval.encoded+7)>>3;
-          }
-          else{
-            LOG_I(NR_RRC, "ue_CapabilityRAT_ContainerList is not present \n");
-          }
-        }
-        itti_send_msg_to_task (TASK_CU_F1, ctxt_pP->module_id, message_p);
-      }
+      // we send the UE capabilities request before RRC connection is complete,
+      // so we cannot have a PDU session yet
+      AssertFatal(UE->established_pdu_sessions_flag == 0, "logic bug: received capabilities while PDU session established\n");
+      // TODO: send UE context modification response with UE capabilities to
+      // allow DU configure CellGroupConfig
+      rrc_gNB_generate_defaultRRCReconfiguration(ctxt_pP, ue_context_p);
 
       break;
 
@@ -2525,233 +2465,6 @@ void rrc_gNB_process_dc_overall_timeout(const module_id_t gnb_mod_idP, x2ap_ENDC
   rrc_remove_nsa_user(rrc, m->rnti);
 }
 
-static void rrc_DU_process_ue_context_setup_request(MessageDef *msg_p, instance_t instance)
-{
-  f1ap_ue_context_setup_t * req=&F1AP_UE_CONTEXT_SETUP_REQ(msg_p);
-  gNB_RRC_INST *rrc = RC.nrrrc[instance];
-  gNB_MAC_INST *mac = RC.nrmac[instance];
-  rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, req->rnti);
-  gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
-  MessageDef *message_p;
-  message_p = itti_alloc_new_message (TASK_RRC_GNB, 0, F1AP_UE_CONTEXT_SETUP_RESP);
-  f1ap_ue_context_setup_t * resp=&F1AP_UE_CONTEXT_SETUP_RESP(message_p);
-  uint32_t incoming_teid = 0;
-
-  if(req->cu_to_du_rrc_information!=NULL){
-    if(req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList!=NULL){
-      LOG_I(NR_RRC, "Length of ue_CapabilityRAT_ContainerList is: %d \n", (int) req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length);
-      struct NR_UE_CapabilityRAT_ContainerList  *ue_CapabilityRAT_ContainerList = NULL;
-      asn_dec_rval_t dec_rval = uper_decode_complete( NULL,
-          &asn_DEF_NR_UE_CapabilityRAT_ContainerList,
-          (void **)&ue_CapabilityRAT_ContainerList,
-          (uint8_t *)req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList,
-          (int) req->cu_to_du_rrc_information->uE_CapabilityRAT_ContainerList_length);
-
-      if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) {
-        AssertFatal(1==0,"UE Capability RAT ContainerList decode error\n");
-        // free the memory
-        SEQUENCE_free( &asn_DEF_NR_UE_CapabilityRAT_ContainerList, ue_CapabilityRAT_ContainerList, 1 );
-        return;
-      }
-      //To fill ue_context.UE_Capability_MRDC, ue_context.UE_Capability_nr ...
-      int NR_index = -1;
-      for(int i = 0;i < ue_CapabilityRAT_ContainerList->list.count; i++){
-        if(ue_CapabilityRAT_ContainerList->list.array[i]->rat_Type ==
-          NR_RAT_Type_nr){
-          LOG_I(NR_RRC, "DU received NR_RAT_Type_nr UE capabilities Info through the UE Context Setup Request from the CU \n");
-          if (UE->UE_Capability_nr) {
-            ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
-            UE->UE_Capability_nr = 0;
-          }
-
-          dec_rval = uper_decode(NULL,
-                                 &asn_DEF_NR_UE_NR_Capability,
-                                 (void **)&UE->UE_Capability_nr,
-                                 ue_CapabilityRAT_ContainerList->list.array[i]->ue_CapabilityRAT_Container.buf,
-                                 ue_CapabilityRAT_ContainerList->list.array[i]->ue_CapabilityRAT_Container.size,
-                                 0,
-                                 0);
-          if(LOG_DEBUGFLAG(DEBUG_ASN1)){
-            xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
-          }
-
-          if((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)){
-            LOG_E(NR_RRC, "UE %04x Failed to decode nr UE capabilities (%zu bytes)\n", req->rnti, dec_rval.consumed);
-            ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
-            UE->UE_Capability_nr = 0;
-          }
-
-          UE->UE_Capability_size = ue_CapabilityRAT_ContainerList->list.array[i]->ue_CapabilityRAT_Container.size;
-          if(NR_index != -1){
-            LOG_E(NR_RRC,"fatal: more than 1 eutra capability\n");
-            exit(1);
-          }
-          NR_index = i;
-        }
-
-        if(ue_CapabilityRAT_ContainerList->list.array[i]->rat_Type ==
-        NR_RAT_Type_eutra_nr){
-          LOG_I(NR_RRC, "DU received NR_RAT_Type_eutra_nr UE capabilities Info through the UE Context Setup Request from the CU \n");
-          if (UE->UE_Capability_MRDC) {
-            ASN_STRUCT_FREE(asn_DEF_NR_UE_MRDC_Capability, UE->UE_Capability_MRDC);
-            UE->UE_Capability_MRDC = 0;
-          }
-          dec_rval = uper_decode(NULL,
-                                 &asn_DEF_NR_UE_MRDC_Capability,
-                                 (void **)&UE->UE_Capability_MRDC,
-                                 ue_CapabilityRAT_ContainerList->list.array[i]->ue_CapabilityRAT_Container.buf,
-                                 ue_CapabilityRAT_ContainerList->list.array[i]->ue_CapabilityRAT_Container.size,
-                                 0,
-                                 0);
-          if(LOG_DEBUGFLAG(DEBUG_ASN1)){
-            xer_fprint(stdout, &asn_DEF_NR_UE_MRDC_Capability, UE->UE_Capability_MRDC);
-          }
-
-          if((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)){
-            LOG_E(NR_RRC, "UE %04x Failed to decode nr UE capabilities (%zu bytes)\n", req->rnti, dec_rval.consumed);
-            ASN_STRUCT_FREE(asn_DEF_NR_UE_MRDC_Capability, UE->UE_Capability_MRDC);
-            UE->UE_Capability_MRDC = 0;
-          }
-          UE->UE_MRDC_Capability_size = ue_CapabilityRAT_ContainerList->list.array[i]->ue_CapabilityRAT_Container.size;
-        }
-
-        if(ue_CapabilityRAT_ContainerList->list.array[i]->rat_Type ==
-        NR_RAT_Type_eutra){
-          //TODO
-        }
-      }
-    }
-  }
-
-
-  /* Configure SRB2 */
-  NR_SRB_ToAddMod_t            *SRB2_config          = NULL;
-  NR_SRB_ToAddModList_t        *SRB_configList       = NULL;
-  uint8_t SRBs_before_new_addition = 0;
-
-  if(req->srbs_to_be_setup_length>0){
-    if (UE->SRB_configList == NULL) {
-      LOG_W(NR_RRC, "The SRB list of the UE context is empty before the addition of new SRB at the DU \n");
-      UE->SRB_configList = CALLOC(1, sizeof(*UE->SRB_configList));
-    }
-    SRB_configList = UE->SRB_configList;
-    SRBs_before_new_addition = SRB_configList->list.count;
-    for (int i=0; i<req->srbs_to_be_setup_length; i++){
-      SRB2_config = CALLOC(1, sizeof(*SRB2_config));
-      SRB2_config->srb_Identity = req->srbs_to_be_setup[i].srb_id;
-      asn1cSeqAdd(&SRB_configList->list, SRB2_config);
-    }
-  }
-
-  /* Configure DRB */
-  NR_DRB_ToAddMod_t            *DRB_config          = NULL;
-  NR_DRB_ToAddModList_t        *DRB_configList      = NULL;
-  uint8_t drb_id_to_setup_start = 0;
-  uint8_t nb_drb_to_setup = 0;
-  long drb_priority[NGAP_MAX_DRBS_PER_UE];
-  if(req->drbs_to_be_setup_length>0){
-    if (UE->DRB_configList == NULL) {
-      UE->DRB_configList = CALLOC(1, sizeof(*UE->DRB_configList));
-    }
-    DRB_configList = UE->DRB_configList;
-    nb_drb_to_setup = req->drbs_to_be_setup_length;
-    for (int i=0; i<req->drbs_to_be_setup_length; i++){
-      DRB_config = CALLOC(1, sizeof(*DRB_config));
-      DRB_config->drb_Identity = req->drbs_to_be_setup[i].drb_id;
-      if (drb_id_to_setup_start == 0) drb_id_to_setup_start = DRB_config->drb_Identity;
-      asn1cSeqAdd(&DRB_configList->list, DRB_config);
-      f1ap_drb_to_be_setup_t drb_p = req->drbs_to_be_setup[i];
-      transport_layer_addr_t addr;
-      memcpy(addr.buffer, &drb_p.up_ul_tnl[0].tl_address, sizeof(drb_p.up_ul_tnl[0].tl_address));
-      addr.length=sizeof(drb_p.up_ul_tnl[0].tl_address)*8;
-      extern instance_t DUuniqInstance;
-      incoming_teid = newGtpuCreateTunnel(DUuniqInstance,
-                                          req->rnti,
-                                          drb_p.drb_id,
-                                          drb_p.drb_id,
-                                          drb_p.up_ul_tnl[0].teid,
-                                          -1, // no qfi
-                                          addr,
-                                          drb_p.up_ul_tnl[0].port,
-                                          DURecvCb,
-                                          NULL);
-      /* TODO: hardcoded to 13 for the time being, to be changed? */
-      drb_priority[DRB_config->drb_Identity-1] = 13;
-    }
-  }
-
-  NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t));
-  if (req->srbs_to_be_setup_length > 0 || req->drbs_to_be_setup_length>0)
-    // FIXME: fill_mastercellGroupConfig() won't fill the right priorities or
-    // bearer IDs for the DRBs
-    fill_mastercellGroupConfig(cellGroupConfig, UE->masterCellGroup, rrc->um_on_default_drb, SRB2_config ? 1 : 0, drb_id_to_setup_start, nb_drb_to_setup, drb_priority);
-  protocol_ctxt_t ctxt = {.rntiMaybeUEid = req->rnti, .module_id = instance, .instance = instance, .enb_flag = 1, .eNB_index = instance};
-  apply_macrlc_config(rrc, ue_context_p, &ctxt);
-  
-  if(req->rrc_container_length > 0){
-    mem_block_t *pdcp_pdu_p = get_free_mem_block(req->rrc_container_length, __func__);
-    memcpy(&pdcp_pdu_p->data[0], req->rrc_container, req->rrc_container_length);
-    du_rlc_data_req(&ctxt, 1, 0x00, 1, 1, 0, req->rrc_container_length, pdcp_pdu_p);
-    LOG_I(F1AP, "Printing RRC Container of UE context setup request: \n");
-    for (int j=0; j<req->rrc_container_length; j++){
-      printf("%02x ", pdcp_pdu_p->data[j]);
-    }
-    printf("\n");
-  }
-
-  /* Fill the UE context setup response ITTI message to send to F1AP */
-  resp->gNB_CU_ue_id = req->gNB_CU_ue_id;
-  resp->rnti = req->rnti;
-  if(DRB_configList){ 
-    if(DRB_configList->list.count > 0){
-      resp->drbs_to_be_setup = calloc(1,DRB_configList->list.count*sizeof(f1ap_drb_to_be_setup_t));
-      resp->drbs_to_be_setup_length = DRB_configList->list.count;
-      for (int i=0; i<DRB_configList->list.count; i++){
-        resp->drbs_to_be_setup[i].drb_id = DRB_configList->list.array[i]->drb_Identity;
-        resp->drbs_to_be_setup[i].rlc_mode = RLC_MODE_AM;
-        resp->drbs_to_be_setup[i].up_dl_tnl[0].teid = incoming_teid;
-        resp->drbs_to_be_setup[i].up_dl_tnl[0].tl_address = inet_addr(mac->eth_params_n.my_addr);
-        resp->drbs_to_be_setup[i].up_dl_tnl_length = 1;
-      }
-    }
-    else{
-      LOG_W(NR_RRC, "No DRB added upon reception of F1 UE context setup request with a DRB to setup list\n");
-    }
-  }
-  if(SRB_configList){
-    if(SRB_configList->list.count >0 && SRBs_before_new_addition < SRB_configList->list.count){
-      resp->srbs_to_be_setup = calloc(1,req->srbs_to_be_setup_length*sizeof(f1ap_srb_to_be_setup_t));
-      resp->srbs_to_be_setup_length = req->srbs_to_be_setup_length;
-      for (int i=SRBs_before_new_addition; i<SRB_configList->list.count; i++){
-        resp->srbs_to_be_setup[i-SRBs_before_new_addition].srb_id = SRB_configList->list.array[i]->srb_Identity;
-      }
-    }
-    else{
-      LOG_W(NR_RRC, "No SRB added upon reception of F1 UE Context setup request at the DU\n");
-    }
-  }
-  else{
-    LOG_W(NR_RRC, "No SRB added upon reception of F1 UE Context setup request at the DU\n");
-  }
-  /* fixme:
-   * Here we should be encoding the updates on cellgroupconfig based on the content of UE capabilities
-   */
-  resp->du_to_cu_rrc_information = calloc(1, sizeof(du_to_cu_rrc_information_t));
-  resp->du_to_cu_rrc_information->cellGroupConfig = calloc(1,1024);
-  asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig,
-                                                  NULL,
-                                                  UE->masterCellGroup, //(void *)cellGroupConfig,
-                                                  resp->du_to_cu_rrc_information->cellGroupConfig,
-                                                  1024);
-  if (enc_rval.encoded == -1) {
-        LOG_E(F1AP,"Could not encode ue_context.masterCellGroup, failed element %s\n",enc_rval.failed_type->name);
-        exit(-1);
-  }
-  resp->du_to_cu_rrc_information->cellGroupConfig_length = (enc_rval.encoded+7)>>3;
-  free(cellGroupConfig);
-  itti_send_msg_to_task(TASK_DU_F1, instance, message_p);
-}
-
 static void rrc_DU_process_ue_context_modification_request(MessageDef *msg_p, instance_t instance)
 {
   f1ap_ue_context_setup_t * req=&F1AP_UE_CONTEXT_MODIFICATION_REQ(msg_p);
@@ -2899,54 +2612,29 @@ static void rrc_DU_process_ue_context_modification_request(MessageDef *msg_p, in
 static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance_t instance)
 {
   f1ap_ue_context_setup_t * resp=&F1AP_UE_CONTEXT_SETUP_RESP(msg_p);
-  protocol_ctxt_t ctxt = {.rntiMaybeUEid = resp->rnti, .module_id = instance, .instance = instance, .enb_flag = 1, .eNB_index = instance};
-  gNB_RRC_INST *rrc = RC.nrrrc[ctxt.module_id];
+  gNB_RRC_INST *rrc = RC.nrrrc[instance];
   rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, resp->rnti);
   gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
-  NR_CellGroupConfig_t *cellGroupConfig = NULL;
-
-  asn_dec_rval_t dec_rval = uper_decode_complete( NULL,
-    &asn_DEF_NR_CellGroupConfig,
-    (void **)&cellGroupConfig,
-    (uint8_t *)resp->du_to_cu_rrc_information->cellGroupConfig,
-    (int) resp->du_to_cu_rrc_information->cellGroupConfig_length);
 
-  if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) {
-    AssertFatal(1==0,"Cell group config decode error\n");
-    // free the memory
-    SEQUENCE_free( &asn_DEF_NR_CellGroupConfig, cellGroupConfig, 1 );
-    return;
-  }
-  //xer_fprint(stdout,&asn_DEF_NR_CellGroupConfig, cellGroupConfig);
+  NR_CellGroupConfig_t *cellGroupConfig = NULL;
+  asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
+                                                 &asn_DEF_NR_CellGroupConfig,
+                                                 (void **)&cellGroupConfig,
+                                                 (uint8_t *)resp->du_to_cu_rrc_information->cellGroupConfig,
+                                                 resp->du_to_cu_rrc_information->cellGroupConfig_length);
+  AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n");
 
-  if (UE->masterCellGroup == NULL) {
-    UE->masterCellGroup = calloc(1, sizeof(NR_CellGroupConfig_t));
-  }
-  if(cellGroupConfig->rlc_BearerToAddModList!=NULL){
-    if (UE->masterCellGroup->rlc_BearerToAddModList != NULL) {
-      int ue_ctxt_rlc_Bearers = UE->masterCellGroup->rlc_BearerToAddModList->list.count;
-      for(int i=ue_ctxt_rlc_Bearers; i<ue_ctxt_rlc_Bearers + cellGroupConfig->rlc_BearerToAddModList->list.count; i++){
-        asn1cSeqAdd(&UE->masterCellGroup->rlc_BearerToAddModList->list, cellGroupConfig->rlc_BearerToAddModList->list.array[i - ue_ctxt_rlc_Bearers]);
-      }
-    } else {
-      LOG_W(NR_RRC, "Empty rlc_BearerToAddModList at ue_context of the CU before filling the updates from UE context setup response \n");
-      UE->masterCellGroup->rlc_BearerToAddModList = calloc(1, sizeof(*cellGroupConfig->rlc_BearerToAddModList));
-      memcpy(UE->masterCellGroup->rlc_BearerToAddModList, cellGroupConfig->rlc_BearerToAddModList, sizeof(*cellGroupConfig->rlc_BearerToAddModList));
-    }
+  if (UE->masterCellGroup) {
+    ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
+    LOG_I(RRC, "UE %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rnti);
   }
-  if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
+  UE->masterCellGroup = cellGroupConfig;
+  if (LOG_DEBUGFLAG(DEBUG_ASN1))
     xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
-  }
 
-  if (UE->established_pdu_sessions_flag == 1) {
-    fill_DRB_configList(&ctxt, ue_context_p);
-    rrc_gNB_generate_dedicatedRRCReconfiguration(&ctxt, ue_context_p, cellGroupConfig);
-  } else {
-    rrc_gNB_generate_defaultRRCReconfiguration(&ctxt, ue_context_p);
-  }
-
-  free(cellGroupConfig->rlc_BearerToAddModList);
-  free(cellGroupConfig);
+  /* at this point, we don't have to do anything: the UE context setup request
+   * includes the Security Command, whose response will trigger the following
+   * messages (UE capability, to be specific) */
 }
 
 static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, instance_t instance)
@@ -3508,10 +3196,6 @@ void *rrc_gnb_task(void *args_p) {
         AssertFatal(NODE_IS_CU(RC.nrrrc[instance]->node_type), "should not receive F1AP_SETUP_REQUEST, need call by CU!\n");
         rrc_gNB_process_f1_setup_req(&F1AP_SETUP_REQ(msg_p));
         break;
-	
-      case F1AP_UE_CONTEXT_SETUP_REQ:
-        rrc_DU_process_ue_context_setup_request(msg_p, instance);
-        break;
 
       case F1AP_UE_CONTEXT_SETUP_RESP:
         rrc_CU_process_ue_context_setup_response(msg_p, instance);
@@ -3583,6 +3267,27 @@ void *rrc_gnb_task(void *args_p) {
   }
 }
 
+static void rrc_deliver_ue_ctxt_setup_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id)
+{
+  DevAssert(deliver_pdu_data != NULL);
+  gNB_RRC_INST *rrc = deliver_pdu_data;
+  f1ap_ue_context_setup_t ue_context_setup_req = {
+    .gNB_CU_ue_id = 0xffffffff, /* filled by F1 for the moment */
+    .gNB_DU_ue_id = 0xffffffff, /* filled by F1 for the moment */
+    .rnti = ue_id,
+    .mcc = rrc->configuration.mcc[0],
+    .mnc = rrc->configuration.mnc[0],
+    .mnc_digit_length = rrc->configuration.mnc_digit_length[0],
+    .nr_cellid = rrc->nr_cellid,
+    .servCellId = 0, /* TODO: correct value? */
+    .srbs_to_be_setup = 0, /* no new SRBs */
+    .drbs_to_be_setup = 0, /* no new DRBs */
+    .rrc_container = (uint8_t*) buf, /* security mode command */
+    .rrc_container_length = size,
+  };
+  rrc->mac_rrc.ue_context_setup_request(&ue_context_setup_req);
+}
+
 //-----------------------------------------------------------------------------
 void
 rrc_gNB_generate_SecurityModeCommand(
@@ -3604,7 +3309,8 @@ rrc_gNB_generate_SecurityModeCommand(
   gNB_RRC_INST *rrc = RC.nrrrc[ctxt_pP->module_id];
   AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n");
 
-  nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, deliver_pdu_srb_f1, rrc);
+  /* the callback will fill the UE context setup request and forward it */
+  nr_pdcp_data_req_srb(ctxt_pP->rntiMaybeUEid, DCCH, rrc_gNB_mui++, size, buffer, rrc_deliver_ue_ctxt_setup_req, rrc);
 }
 
 void
diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c
index bf69aa703b69c3afee51c12a2d60dd5172685c2c..250402a2cfa3ce97c0ee3db982dd05c3c8673a8b 100644
--- a/openair2/RRC/NR/rrc_gNB_nsa.c
+++ b/openair2/RRC/NR/rrc_gNB_nsa.c
@@ -401,8 +401,11 @@ void rrc_remove_nsa_user(gNB_RRC_INST *rrc, int rnti) {
 
   rrc_rlc_remove_ue(&ctxt);
 
-  // WHAT A RACE CONDITION
+  // lock the scheduler before removing the UE. Note: mac_remove_nr_ue() checks
+  // that the scheduler is actually locked!
+  NR_SCHED_LOCK(&RC.nrmac[rrc->module_id]->sched_lock);
   mac_remove_nr_ue(RC.nrmac[rrc->module_id], rnti);
+  NR_SCHED_UNLOCK(&RC.nrmac[rrc->module_id]->sched_lock);
   gtpv1u_enb_delete_tunnel_req_t tmp={0};
   tmp.rnti=rnti;
   tmp.from_gnb=1;