[posix] replace fd_sets with the mainloop context (#9127)

(cherry picked from commit cc09029188f5b4a9fc8c068b0c0b73446b99a258)
Change-Id: I84d61407e942139d93324a6f9ae3df2e1740b2c4
diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp
index d14f7c0..332eff4 100644
--- a/src/lib/spinel/radio_spinel.hpp
+++ b/src/lib/spinel/radio_spinel.hpp
@@ -91,10 +91,9 @@
  *
  *    // This method performs radio driver processing.
  *
- *    // @param[in]   aContext        The context containing fd_sets.
- *    //                              The type is specified by the user in template parameters.
+ *    // @param[in]   aContext  The process context.
  *
- *    void Process(const ProcessContextType &aContext);
+ *    void Process(const void *aContext);
  *
  *
  *    // This method deinitializes the interface to the RCP.
@@ -102,7 +101,7 @@
  *    void Deinit(void);
  * };
  */
-template <typename InterfaceType, typename ProcessContextType> class RadioSpinel
+template <typename InterfaceType> class RadioSpinel
 {
 public:
     /**
@@ -562,7 +561,7 @@
      * @param[in]  aContext   The process context.
      *
      */
-    void Process(const ProcessContextType &aContext);
+    void Process(const void *aContext);
 
     /**
      * This method returns the underlying spinel interface.
diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp
index c9c6765..d4928c8 100644
--- a/src/lib/spinel/radio_spinel_impl.hpp
+++ b/src/lib/spinel/radio_spinel_impl.hpp
@@ -162,14 +162,13 @@
     }
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleReceivedFrame(void *aContext)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::HandleReceivedFrame(void *aContext)
 {
     static_cast<RadioSpinel *>(aContext)->HandleReceivedFrame();
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-RadioSpinel<InterfaceType, ProcessContextType>::RadioSpinel(void)
+template <typename InterfaceType>
+RadioSpinel<InterfaceType>::RadioSpinel(void)
     : mInstance(nullptr)
     , mRxFrameBuffer()
     , mSpinelInterface(HandleReceivedFrame, this, mRxFrameBuffer)
@@ -217,10 +216,8 @@
     memset(&mRadioSpinelMetrics, 0, sizeof(mRadioSpinelMetrics));
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::Init(bool aResetRadio,
-                                                          bool aRestoreDatasetFromNcp,
-                                                          bool aSkipRcpCompatibilityCheck)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::Init(bool aResetRadio, bool aRestoreDatasetFromNcp, bool aSkipRcpCompatibilityCheck)
 {
     otError error = OT_ERROR_NONE;
     bool    supportsRcpApiVersion;
@@ -263,8 +260,7 @@
     SuccessOrDie(error);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::ResetRcp(bool aResetRadio)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::ResetRcp(bool aResetRadio)
 {
     bool hardwareReset;
     bool resetDone = false;
@@ -300,8 +296,7 @@
     }
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::CheckSpinelVersion(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::CheckSpinelVersion(void)
 {
     otError      error = OT_ERROR_NONE;
     unsigned int versionMajor;
@@ -323,9 +318,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-bool RadioSpinel<InterfaceType, ProcessContextType>::IsRcp(bool &aSupportsRcpApiVersion,
-                                                           bool &aSupportsRcpMinHostApiVersion)
+template <typename InterfaceType>
+bool RadioSpinel<InterfaceType>::IsRcp(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion)
 {
     uint8_t        capsBuffer[kCapsBufferSize];
     const uint8_t *capsData         = capsBuffer;
@@ -384,8 +378,7 @@
     return isRcp;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::CheckRadioCapabilities(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::CheckRadioCapabilities(void)
 {
     const otRadioCaps kRequiredRadioCaps =
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
@@ -421,9 +414,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::CheckRcpApiVersion(bool aSupportsRcpApiVersion,
-                                                                           bool aSupportsRcpMinHostApiVersion)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::CheckRcpApiVersion(bool aSupportsRcpApiVersion, bool aSupportsRcpMinHostApiVersion)
 {
     otError error = OT_ERROR_NONE;
 
@@ -473,8 +465,7 @@
 }
 
 #if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::RestoreDatasetFromNcp(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::RestoreDatasetFromNcp(void)
 {
     otError error = OT_ERROR_NONE;
 
@@ -492,16 +483,14 @@
 }
 #endif
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::Deinit(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::Deinit(void)
 {
     mSpinelInterface.Deinit();
     // This allows implementing pseudo reset.
     new (this) RadioSpinel();
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleReceivedFrame(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::HandleReceivedFrame(void)
 {
     otError        error = OT_ERROR_NONE;
     uint8_t        header;
@@ -534,8 +523,8 @@
     UpdateParseErrorCount(error);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffer)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffer)
 {
     spinel_prop_key_t key;
     spinel_size_t     len = 0;
@@ -589,8 +578,8 @@
     LogIfFail("Error processing notification", error);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleNotification(const uint8_t *aFrame, uint16_t aLength)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::HandleNotification(const uint8_t *aFrame, uint16_t aLength)
 {
     spinel_prop_key_t key;
     spinel_size_t     len = 0;
@@ -611,8 +600,8 @@
     LogIfFail("Error processing saved notification", error);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleResponse(const uint8_t *aBuffer, uint16_t aLength)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::HandleResponse(const uint8_t *aBuffer, uint16_t aLength)
 {
     spinel_prop_key_t key;
     uint8_t          *data   = nullptr;
@@ -654,8 +643,8 @@
 }
 
 #if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ThreadDatasetHandler(const uint8_t *aBuffer, uint16_t aLength)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::ThreadDatasetHandler(const uint8_t *aBuffer, uint16_t aLength)
 {
     otError              error = OT_ERROR_NONE;
     otOperationalDataset opDataset;
@@ -818,11 +807,11 @@
 }
 #endif // #if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleWaitingResponse(uint32_t          aCommand,
-                                                                           spinel_prop_key_t aKey,
-                                                                           const uint8_t    *aBuffer,
-                                                                           uint16_t          aLength)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::HandleWaitingResponse(uint32_t          aCommand,
+                                                       spinel_prop_key_t aKey,
+                                                       const uint8_t    *aBuffer,
+                                                       uint16_t          aLength)
 {
     if (aKey == SPINEL_PROP_LAST_STATUS)
     {
@@ -887,10 +876,8 @@
     LogIfFail("Error processing result", mError);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleValueIs(spinel_prop_key_t aKey,
-                                                                   const uint8_t    *aBuffer,
-                                                                   uint16_t          aLength)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength)
 {
     otError        error = OT_ERROR_NONE;
     spinel_ssize_t unpacked;
@@ -995,11 +982,11 @@
     LogIfFail("Failed to handle ValueIs", error);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ParseRadioFrame(otRadioFrame   &aFrame,
-                                                                        const uint8_t  *aBuffer,
-                                                                        uint16_t        aLength,
-                                                                        spinel_ssize_t &aUnpacked)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::ParseRadioFrame(otRadioFrame   &aFrame,
+                                                    const uint8_t  *aBuffer,
+                                                    uint16_t        aLength,
+                                                    spinel_ssize_t &aUnpacked)
 {
     otError        error        = OT_ERROR_NONE;
     uint16_t       flags        = 0;
@@ -1068,8 +1055,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::ProcessFrameQueue(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::ProcessFrameQueue(void)
 {
     uint8_t *frame = nullptr;
     uint16_t length;
@@ -1082,8 +1068,7 @@
     mRxFrameBuffer.ClearSavedFrames();
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::RadioReceive(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::RadioReceive(void)
 {
     if (!mIsPromiscuous)
     {
@@ -1115,10 +1100,8 @@
     return;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::TransmitDone(otRadioFrame *aFrame,
-                                                                  otRadioFrame *aAckFrame,
-                                                                  otError       aError)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
 {
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
     if (otPlatDiagModeGet())
@@ -1132,8 +1115,7 @@
     }
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::ProcessRadioStateMachine(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::ProcessRadioStateMachine(void)
 {
     if (mState == kStateTransmitDone)
     {
@@ -1150,8 +1132,7 @@
     }
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::Process(const ProcessContextType &aContext)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::Process(const void *aContext)
 {
     if (mRxFrameBuffer.HasSavedFrame())
     {
@@ -1173,8 +1154,7 @@
     CalcRcpTimeOffset();
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetPromiscuous(bool aEnable)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetPromiscuous(bool aEnable)
 {
     otError error;
 
@@ -1186,8 +1166,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetShortAddress(uint16_t aAddress)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetShortAddress(uint16_t aAddress)
 {
     otError error = OT_ERROR_NONE;
 
@@ -1199,12 +1178,12 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetMacKey(uint8_t                 aKeyIdMode,
-                                                                  uint8_t                 aKeyId,
-                                                                  const otMacKeyMaterial *aPrevKey,
-                                                                  const otMacKeyMaterial *aCurrKey,
-                                                                  const otMacKeyMaterial *aNextKey)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::SetMacKey(uint8_t                 aKeyIdMode,
+                                              uint8_t                 aKeyId,
+                                              const otMacKeyMaterial *aPrevKey,
+                                              const otMacKeyMaterial *aCurrKey,
+                                              const otMacKeyMaterial *aNextKey)
 {
     otError error;
     size_t  aKeySize;
@@ -1242,8 +1221,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetMacFrameCounter(uint32_t aMacFrameCounter, bool aSetIfLarger)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::SetMacFrameCounter(uint32_t aMacFrameCounter, bool aSetIfLarger)
 {
     otError error;
 
@@ -1254,16 +1233,15 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetIeeeEui64(uint8_t *aIeeeEui64)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::GetIeeeEui64(uint8_t *aIeeeEui64)
 {
     memcpy(aIeeeEui64, mIeeeEui64.m8, sizeof(mIeeeEui64.m8));
 
     return OT_ERROR_NONE;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetExtendedAddress(const otExtAddress &aExtAddress)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::SetExtendedAddress(const otExtAddress &aExtAddress)
 {
     otError error;
 
@@ -1274,8 +1252,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetPanId(uint16_t aPanId)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetPanId(uint16_t aPanId)
 {
     otError error = OT_ERROR_NONE;
 
@@ -1287,14 +1264,12 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::EnableSrcMatch(bool aEnable)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::EnableSrcMatch(bool aEnable)
 {
     return Set(SPINEL_PROP_MAC_SRC_MATCH_ENABLED, SPINEL_DATATYPE_BOOL_S, aEnable);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::AddSrcMatchShortEntry(uint16_t aShortAddress)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::AddSrcMatchShortEntry(uint16_t aShortAddress)
 {
     otError error;
 
@@ -1318,8 +1293,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::AddSrcMatchExtEntry(const otExtAddress &aExtAddress)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::AddSrcMatchExtEntry(const otExtAddress &aExtAddress)
 {
     otError error;
 
@@ -1344,8 +1319,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ClearSrcMatchShortEntry(uint16_t aShortAddress)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::ClearSrcMatchShortEntry(uint16_t aShortAddress)
 {
     otError error;
 
@@ -1367,8 +1341,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ClearSrcMatchExtEntry(const otExtAddress &aExtAddress)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::ClearSrcMatchExtEntry(const otExtAddress &aExtAddress)
 {
     otError error;
 
@@ -1391,8 +1365,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ClearSrcMatchShortEntries(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::ClearSrcMatchShortEntries(void)
 {
     otError error;
 
@@ -1406,8 +1379,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ClearSrcMatchExtEntries(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::ClearSrcMatchExtEntries(void)
 {
     otError error;
 
@@ -1421,8 +1393,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetTransmitPower(int8_t &aPower)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::GetTransmitPower(int8_t &aPower)
 {
     otError error = Get(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, &aPower);
 
@@ -1430,8 +1401,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetCcaEnergyDetectThreshold(int8_t &aThreshold)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::GetCcaEnergyDetectThreshold(int8_t &aThreshold)
 {
     otError error = Get(SPINEL_PROP_PHY_CCA_THRESHOLD, SPINEL_DATATYPE_INT8_S, &aThreshold);
 
@@ -1439,8 +1409,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetFemLnaGain(int8_t &aGain)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::GetFemLnaGain(int8_t &aGain)
 {
     otError error = Get(SPINEL_PROP_PHY_FEM_LNA_GAIN, SPINEL_DATATYPE_INT8_S, &aGain);
 
@@ -1448,8 +1417,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-int8_t RadioSpinel<InterfaceType, ProcessContextType>::GetRssi(void)
+template <typename InterfaceType> int8_t RadioSpinel<InterfaceType>::GetRssi(void)
 {
     int8_t  rssi  = OT_RADIO_RSSI_INVALID;
     otError error = Get(SPINEL_PROP_PHY_RSSI, SPINEL_DATATYPE_INT8_S, &rssi);
@@ -1459,8 +1427,7 @@
 }
 
 #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetCoexEnabled(bool aEnabled)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetCoexEnabled(bool aEnabled)
 {
     otError error;
 
@@ -1475,8 +1442,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-bool RadioSpinel<InterfaceType, ProcessContextType>::IsCoexEnabled(void)
+template <typename InterfaceType> bool RadioSpinel<InterfaceType>::IsCoexEnabled(void)
 {
     bool    enabled;
     otError error = Get(SPINEL_PROP_RADIO_COEX_ENABLE, SPINEL_DATATYPE_BOOL_S, &enabled);
@@ -1485,8 +1451,7 @@
     return enabled;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetCoexMetrics(otRadioCoexMetrics &aCoexMetrics)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::GetCoexMetrics(otRadioCoexMetrics &aCoexMetrics)
 {
     otError error;
 
@@ -1526,8 +1491,7 @@
 }
 #endif
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetTransmitPower(int8_t aPower)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetTransmitPower(int8_t aPower)
 {
     otError error;
 
@@ -1543,8 +1507,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetCcaEnergyDetectThreshold(int8_t aThreshold)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetCcaEnergyDetectThreshold(int8_t aThreshold)
 {
     otError error;
 
@@ -1560,8 +1523,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetFemLnaGain(int8_t aGain)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetFemLnaGain(int8_t aGain)
 {
     otError error;
 
@@ -1577,8 +1539,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
 {
     otError error;
 
@@ -1600,8 +1562,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Get(spinel_prop_key_t aKey, const char *aFormat, ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::Get(spinel_prop_key_t aKey, const char *aFormat, ...)
 {
     otError error;
 
@@ -1623,12 +1585,12 @@
 }
 
 // This is not a normal use case for VALUE_GET command and should be only used to get RCP timestamp with dummy payload
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetWithParam(spinel_prop_key_t aKey,
-                                                                     const uint8_t    *aParam,
-                                                                     spinel_size_t     aParamSize,
-                                                                     const char       *aFormat,
-                                                                     ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::GetWithParam(spinel_prop_key_t aKey,
+                                                 const uint8_t    *aParam,
+                                                 spinel_size_t     aParamSize,
+                                                 const char       *aFormat,
+                                                 ...)
 {
     otError error;
 
@@ -1650,8 +1612,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Set(spinel_prop_key_t aKey, const char *aFormat, ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::Set(spinel_prop_key_t aKey, const char *aFormat, ...)
 {
     otError error;
 
@@ -1673,8 +1635,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Insert(spinel_prop_key_t aKey, const char *aFormat, ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::Insert(spinel_prop_key_t aKey, const char *aFormat, ...)
 {
     otError error;
 
@@ -1696,8 +1658,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Remove(spinel_prop_key_t aKey, const char *aFormat, ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::Remove(spinel_prop_key_t aKey, const char *aFormat, ...)
 {
     otError error;
 
@@ -1719,8 +1681,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::WaitResponse(bool aHandleRcpTimeout)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::WaitResponse(bool aHandleRcpTimeout)
 {
     uint64_t end = otPlatTimeGet() + kMaxWaitTime * US_PER_MS;
 
@@ -1750,8 +1711,7 @@
     return mError;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-spinel_tid_t RadioSpinel<InterfaceType, ProcessContextType>::GetNextTid(void)
+template <typename InterfaceType> spinel_tid_t RadioSpinel<InterfaceType>::GetNextTid(void)
 {
     spinel_tid_t tid = mCmdNextTid;
 
@@ -1775,8 +1735,7 @@
     return tid;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SendReset(uint8_t aResetType)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SendReset(uint8_t aResetType)
 {
     otError        error = OT_ERROR_NONE;
     uint8_t        buffer[kMaxSpinelFrame];
@@ -1795,12 +1754,12 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SendCommand(uint32_t          aCommand,
-                                                                    spinel_prop_key_t aKey,
-                                                                    spinel_tid_t      tid,
-                                                                    const char       *aFormat,
-                                                                    va_list           args)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::SendCommand(uint32_t          aCommand,
+                                                spinel_prop_key_t aKey,
+                                                spinel_tid_t      tid,
+                                                const char       *aFormat,
+                                                va_list           args)
 {
     otError        error = OT_ERROR_NONE;
     uint8_t        buffer[kMaxSpinelFrame];
@@ -1831,11 +1790,11 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::RequestV(uint32_t          command,
-                                                                 spinel_prop_key_t aKey,
-                                                                 const char       *aFormat,
-                                                                 va_list           aArgs)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::RequestV(uint32_t          command,
+                                             spinel_prop_key_t aKey,
+                                             const char       *aFormat,
+                                             va_list           aArgs)
 {
     otError      error = OT_ERROR_NONE;
     spinel_tid_t tid   = GetNextTid();
@@ -1863,11 +1822,8 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Request(uint32_t          aCommand,
-                                                                spinel_prop_key_t aKey,
-                                                                const char       *aFormat,
-                                                                ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::Request(uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, ...)
 {
     va_list args;
     va_start(args, aFormat);
@@ -1876,12 +1832,12 @@
     return status;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::RequestWithPropertyFormat(const char       *aPropertyFormat,
-                                                                                  uint32_t          aCommand,
-                                                                                  spinel_prop_key_t aKey,
-                                                                                  const char       *aFormat,
-                                                                                  ...)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::RequestWithPropertyFormat(const char       *aPropertyFormat,
+                                                              uint32_t          aCommand,
+                                                              spinel_prop_key_t aKey,
+                                                              const char       *aFormat,
+                                                              ...)
 {
     otError error;
     va_list args;
@@ -1893,12 +1849,12 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::RequestWithPropertyFormatV(const char       *aPropertyFormat,
-                                                                                   uint32_t          aCommand,
-                                                                                   spinel_prop_key_t aKey,
-                                                                                   const char       *aFormat,
-                                                                                   va_list           aArgs)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::RequestWithPropertyFormatV(const char       *aPropertyFormat,
+                                                               uint32_t          aCommand,
+                                                               spinel_prop_key_t aKey,
+                                                               const char       *aFormat,
+                                                               va_list           aArgs)
 {
     otError error;
 
@@ -1909,12 +1865,12 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::RequestWithExpectedCommandV(uint32_t          aExpectedCommand,
-                                                                                    uint32_t          aCommand,
-                                                                                    spinel_prop_key_t aKey,
-                                                                                    const char       *aFormat,
-                                                                                    va_list           aArgs)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::RequestWithExpectedCommandV(uint32_t          aExpectedCommand,
+                                                                uint32_t          aCommand,
+                                                                spinel_prop_key_t aKey,
+                                                                const char       *aFormat,
+                                                                va_list           aArgs)
 {
     otError error;
 
@@ -1925,11 +1881,11 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleTransmitDone(uint32_t          aCommand,
-                                                                        spinel_prop_key_t aKey,
-                                                                        const uint8_t    *aBuffer,
-                                                                        uint16_t          aLength)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::HandleTransmitDone(uint32_t          aCommand,
+                                                    spinel_prop_key_t aKey,
+                                                    const uint8_t    *aBuffer,
+                                                    uint16_t          aLength)
 {
     otError         error         = OT_ERROR_NONE;
     spinel_status_t status        = SPINEL_STATUS_OK;
@@ -1991,8 +1947,7 @@
     LogIfFail("Handle transmit done failed", error);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Transmit(otRadioFrame &aFrame)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::Transmit(otRadioFrame &aFrame)
 {
     otError error = OT_ERROR_INVALID_STATE;
 
@@ -2034,8 +1989,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Receive(uint8_t aChannel)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::Receive(uint8_t aChannel)
 {
     otError error = OT_ERROR_NONE;
 
@@ -2066,8 +2020,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Sleep(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::Sleep(void)
 {
     otError error = OT_ERROR_NONE;
 
@@ -2092,8 +2045,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Enable(otInstance *aInstance)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::Enable(otInstance *aInstance)
 {
     otError error = OT_ERROR_NONE;
 
@@ -2118,8 +2070,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::Disable(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::Disable(void)
 {
     otError error = OT_ERROR_NONE;
 
@@ -2135,10 +2086,8 @@
 }
 
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::PlatDiagProcess(const char *aString,
-                                                                        char       *aOutput,
-                                                                        size_t      aOutputMaxLen)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::PlatDiagProcess(const char *aString, char *aOutput, size_t aOutputMaxLen)
 {
     otError error;
 
@@ -2154,8 +2103,7 @@
 }
 #endif
 
-template <typename InterfaceType, typename ProcessContextType>
-uint32_t RadioSpinel<InterfaceType, ProcessContextType>::GetRadioChannelMask(bool aPreferred)
+template <typename InterfaceType> uint32_t RadioSpinel<InterfaceType>::GetRadioChannelMask(bool aPreferred)
 {
     uint8_t        maskBuffer[kChannelMaskBufferSize];
     otError        error       = OT_ERROR_NONE;
@@ -2188,8 +2136,7 @@
     return channelMask;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otRadioState RadioSpinel<InterfaceType, ProcessContextType>::GetState(void) const
+template <typename InterfaceType> otRadioState RadioSpinel<InterfaceType>::GetState(void) const
 {
     static const otRadioState sOtRadioStateMap[] = {
         OT_RADIO_STATE_DISABLED, OT_RADIO_STATE_SLEEP,    OT_RADIO_STATE_RECEIVE,
@@ -2199,8 +2146,7 @@
     return sOtRadioStateMap[mState];
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::CalcRcpTimeOffset(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::CalcRcpTimeOffset(void)
 {
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
     otError        error = OT_ERROR_NONE;
@@ -2261,20 +2207,17 @@
 #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-uint64_t RadioSpinel<InterfaceType, ProcessContextType>::GetNow(void)
+template <typename InterfaceType> uint64_t RadioSpinel<InterfaceType>::GetNow(void)
 {
     return (mIsTimeSynced) ? (otPlatTimeGet() + mRadioTimeOffset) : UINT64_MAX;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-uint32_t RadioSpinel<InterfaceType, ProcessContextType>::GetBusSpeed(void) const
+template <typename InterfaceType> uint32_t RadioSpinel<InterfaceType>::GetBusSpeed(void) const
 {
     return mSpinelInterface.GetBusSpeed();
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleRcpUnexpectedReset(spinel_status_t aStatus)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::HandleRcpUnexpectedReset(spinel_status_t aStatus)
 {
     OT_UNUSED_VARIABLE(aStatus);
 
@@ -2290,8 +2233,7 @@
 #endif
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::HandleRcpTimeout(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::HandleRcpTimeout(void)
 {
     mRadioSpinelMetrics.mRcpTimeoutCount++;
 
@@ -2302,8 +2244,7 @@
 #endif
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::RecoverFromRcpFailure(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::RecoverFromRcpFailure(void)
 {
 #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
     constexpr int16_t kMaxFailureCount = OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT;
@@ -2373,8 +2314,7 @@
 }
 
 #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::RestoreProperties(void)
+template <typename InterfaceType> void RadioSpinel<InterfaceType>::RestoreProperties(void)
 {
     Settings::NetworkInfo networkInfo;
 
@@ -2453,8 +2393,8 @@
 }
 #endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower)
 {
     otError error = OT_ERROR_NONE;
     VerifyOrExit(aChannel >= Radio::kChannelMin && aChannel <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS);
@@ -2465,8 +2405,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetRadioRegion(uint16_t aRegionCode)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::SetRadioRegion(uint16_t aRegionCode)
 {
     otError error;
 
@@ -2486,8 +2425,7 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::GetRadioRegion(uint16_t *aRegionCode)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::GetRadioRegion(uint16_t *aRegionCode)
 {
     otError error = OT_ERROR_NONE;
 
@@ -2499,10 +2437,10 @@
 }
 
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ConfigureEnhAckProbing(otLinkMetrics        aLinkMetrics,
-                                                                               const otShortAddress aShortAddress,
-                                                                               const otExtAddress  &aExtAddress)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::ConfigureEnhAckProbing(otLinkMetrics        aLinkMetrics,
+                                                           const otShortAddress aShortAddress,
+                                                           const otExtAddress  &aExtAddress)
 {
     otError error = OT_ERROR_NONE;
     uint8_t flags = 0;
@@ -2536,8 +2474,7 @@
 #endif
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-uint8_t RadioSpinel<InterfaceType, ProcessContextType>::GetCslAccuracy(void)
+template <typename InterfaceType> uint8_t RadioSpinel<InterfaceType>::GetCslAccuracy(void)
 {
     uint8_t accuracy = UINT8_MAX;
     otError error    = Get(SPINEL_PROP_RCP_CSL_ACCURACY, SPINEL_DATATYPE_UINT8_S, &accuracy);
@@ -2548,8 +2485,7 @@
 #endif
 
 #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-uint8_t RadioSpinel<InterfaceType, ProcessContextType>::GetCslUncertainty(void)
+template <typename InterfaceType> uint8_t RadioSpinel<InterfaceType>::GetCslUncertainty(void)
 {
     uint8_t uncertainty = UINT8_MAX;
     otError error       = Get(SPINEL_PROP_RCP_CSL_UNCERTAINTY, SPINEL_DATATYPE_UINT8_S, &uncertainty);
@@ -2560,11 +2496,11 @@
 #endif
 
 #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::AddCalibratedPower(uint8_t        aChannel,
-                                                                           int16_t        aActualPower,
-                                                                           const uint8_t *aRawPowerSetting,
-                                                                           uint16_t       aRawPowerSettingLength)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::AddCalibratedPower(uint8_t        aChannel,
+                                                       int16_t        aActualPower,
+                                                       const uint8_t *aRawPowerSetting,
+                                                       uint16_t       aRawPowerSettingLength)
 {
     otError error;
 
@@ -2577,14 +2513,13 @@
     return error;
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::ClearCalibratedPowers(void)
+template <typename InterfaceType> otError RadioSpinel<InterfaceType>::ClearCalibratedPowers(void)
 {
     return Set(SPINEL_PROP_PHY_CALIBRATED_POWER, nullptr);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-otError RadioSpinel<InterfaceType, ProcessContextType>::SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower)
+template <typename InterfaceType>
+otError RadioSpinel<InterfaceType>::SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower)
 {
     otError error = OT_ERROR_NONE;
     VerifyOrExit(aChannel >= Radio::kChannelMin && aChannel <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS);
@@ -2596,8 +2531,8 @@
 }
 #endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
 
-template <typename InterfaceType, typename ProcessContextType>
-uint32_t RadioSpinel<InterfaceType, ProcessContextType>::Snprintf(char *aDest, uint32_t aSize, const char *aFormat, ...)
+template <typename InterfaceType>
+uint32_t RadioSpinel<InterfaceType>::Snprintf(char *aDest, uint32_t aSize, const char *aFormat, ...)
 {
     int     len;
     va_list args;
@@ -2609,8 +2544,8 @@
     return (len < 0) ? 0 : Min(static_cast<uint32_t>(len), aSize - 1);
 }
 
-template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::LogSpinelFrame(const uint8_t *aFrame, uint16_t aLength, bool aTx)
+template <typename InterfaceType>
+void RadioSpinel<InterfaceType>::LogSpinelFrame(const uint8_t *aFrame, uint16_t aLength, bool aTx)
 {
     otError           error                               = OT_ERROR_NONE;
     char              buf[OPENTHREAD_CONFIG_LOG_MAX_SIZE] = {0};
diff --git a/src/lib/spinel/spinel_interface.hpp b/src/lib/spinel/spinel_interface.hpp
index ed32225..78b21dd 100644
--- a/src/lib/spinel/spinel_interface.hpp
+++ b/src/lib/spinel/spinel_interface.hpp
@@ -37,6 +37,7 @@
 
 #include "lib/hdlc/hdlc.hpp"
 #include "lib/spinel/spinel.h"
+#include "lib/url/url.hpp"
 
 namespace ot {
 namespace Spinel {
@@ -125,21 +126,18 @@
     /**
      * Updates the file descriptor sets with file descriptors used by the radio driver.
      *
-     * @param[in,out]  aReadFdSet   A reference to the read file descriptors.
-     * @param[in,out]  aWriteFdSet  A reference to the write file descriptors.
-     * @param[in,out]  aMaxFd       A reference to the max file descriptor.
-     * @param[in,out]  aTimeout     A reference to the timeout.
+     * @param[in,out]   aMainloopContext  A pointer to the mainloop context.
      *
      */
-    virtual void UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout) = 0;
+    virtual void UpdateFdSet(void *aMainloopContext) = 0;
 
     /**
      * Performs radio driver processing.
      *
-     * @param[in]  aContext  The context containing fd_sets.
+     * @param[in]   aMainloopContext  A pointer to the mainloop context.
      *
      */
-    virtual void Process(const RadioProcessContext &aContext) = 0;
+    virtual void Process(const void *aMainloopContext) = 0;
 
     /**
      * Returns the bus speed between the host and the radio.
diff --git a/src/posix/platform/hdlc_interface.cpp b/src/posix/platform/hdlc_interface.cpp
index a37427e..7390c89 100644
--- a/src/posix/platform/hdlc_interface.cpp
+++ b/src/posix/platform/hdlc_interface.cpp
@@ -334,25 +334,44 @@
     return error;
 }
 
-void HdlcInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
+void HdlcInterface::UpdateFdSet(void *aMainloopContext)
 {
-    OT_UNUSED_VARIABLE(aWriteFdSet);
-    OT_UNUSED_VARIABLE(aTimeout);
+    otSysMainloopContext *context = reinterpret_cast<otSysMainloopContext *>(aMainloopContext);
 
-    FD_SET(mSockFd, &aReadFdSet);
+    assert(context != nullptr);
 
-    if (aMaxFd < mSockFd)
+    FD_SET(mSockFd, &context->mReadFdSet);
+
+    if (context->mMaxFd < mSockFd)
     {
-        aMaxFd = mSockFd;
+        context->mMaxFd = mSockFd;
     }
 }
 
-void HdlcInterface::Process(const RadioProcessContext &aContext)
+void HdlcInterface::Process(const void *aMainloopContext)
 {
-    if (FD_ISSET(mSockFd, aContext.mReadFdSet))
+#if OPENTHREAD_POSIX_VIRTUAL_TIME
+    /**
+     * Process read data (decode the data).
+     *
+     * Is intended only for virtual time simulation. Its behavior is similar to `Read()` but instead of
+     * reading the data from the radio socket, it uses the given data in @p `event`.
+     */
+    const VirtualTimeEvent *event = reinterpret_cast<const VirtualTimeEvent *>(aMainloopContext);
+
+    assert(event != nullptr);
+
+    Decode(event->mData, event->mDataLength);
+#else
+    const otSysMainloopContext *context = reinterpret_cast<const otSysMainloopContext *>(aMainloopContext);
+
+    assert(context != nullptr);
+
+    if (FD_ISSET(mSockFd, &context->mReadFdSet))
     {
         Read();
     }
+#endif
 }
 
 otError HdlcInterface::WaitForWritable(void)
diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp
index 23e9452..1c69e7c 100644
--- a/src/posix/platform/hdlc_interface.hpp
+++ b/src/posix/platform/hdlc_interface.hpp
@@ -118,34 +118,18 @@
     /**
      * This method updates the file descriptor sets with file descriptors used by the radio driver.
      *
-     * @param[in,out]  aReadFdSet   A reference to the read file descriptors.
-     * @param[in,out]  aWriteFdSet  A reference to the write file descriptors.
-     * @param[in,out]  aMaxFd       A reference to the max file descriptor.
-     * @param[in,out]  aTimeout     A reference to the timeout.
+     * @param[in,out]   aMainloopContext  A pointer to the mainloop context containing fd_sets.
      *
      */
-    void UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout);
+    void UpdateFdSet(void *aMainloopContext);
 
     /**
      * This method performs radio driver processing.
      *
-     * @param[in]   aContext        The context containing fd_sets.
+     * @param[in]   aMainloopContext  A pointer to the mainloop context containing fd_sets.
      *
      */
-    void Process(const RadioProcessContext &aContext);
-
-#if OPENTHREAD_POSIX_VIRTUAL_TIME
-    /**
-     * This method process read data (decode the data).
-     *
-     * This method is intended only for virtual time simulation. Its behavior is similar to `Read()` but instead of
-     * reading the data from the radio socket, it uses the given data in @p `aEvent`.
-     *
-     * @param[in] aEvent   The data event.
-     *
-     */
-    void Process(const VirtualTimeEvent &aEvent) { Decode(aEvent.mData, aEvent.mDataLength); }
-#endif
+    void Process(const void *aMainloopContext);
 
     /**
      * This method returns the bus speed between the host and the radio.
diff --git a/src/posix/platform/netif.cpp b/src/posix/platform/netif.cpp
index 3b2e908..1f238cf 100644
--- a/src/posix/platform/netif.cpp
+++ b/src/posix/platform/netif.cpp
@@ -1947,93 +1947,92 @@
     gNetifIndex = 0;
 }
 
-void platformNetifUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int *aMaxFd)
+void platformNetifUpdateFdSet(otSysMainloopContext *aContext)
 {
-    OT_UNUSED_VARIABLE(aWriteFdSet);
-
     VerifyOrExit(gNetifIndex > 0);
 
+    assert(aContext != nullptr);
     assert(sTunFd >= 0);
     assert(sNetlinkFd >= 0);
     assert(sIpFd >= 0);
 
-    FD_SET(sTunFd, aReadFdSet);
-    FD_SET(sTunFd, aErrorFdSet);
-    FD_SET(sNetlinkFd, aReadFdSet);
-    FD_SET(sNetlinkFd, aErrorFdSet);
+    FD_SET(sTunFd, &aContext->mReadFdSet);
+    FD_SET(sTunFd, &aContext->mErrorFdSet);
+    FD_SET(sNetlinkFd, &aContext->mReadFdSet);
+    FD_SET(sNetlinkFd, &aContext->mErrorFdSet);
 #if OPENTHREAD_POSIX_USE_MLD_MONITOR
-    FD_SET(sMLDMonitorFd, aReadFdSet);
-    FD_SET(sMLDMonitorFd, aErrorFdSet);
+    FD_SET(sMLDMonitorFd, &aContext->mReadFdSet);
+    FD_SET(sMLDMonitorFd, &aContext->mErrorFdSet);
 #endif
 
 #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
-    gResolver.UpdateFdSet(aReadFdSet, aErrorFdSet, aMaxFd);
+    gResolver.UpdateFdSet(*aContext);
 #endif
 
-    if (sTunFd > *aMaxFd)
+    if (sTunFd > aContext->mMaxFd)
     {
-        *aMaxFd = sTunFd;
+        aContext->mMaxFd = sTunFd;
     }
 
-    if (sNetlinkFd > *aMaxFd)
+    if (sNetlinkFd > aContext->mMaxFd)
     {
-        *aMaxFd = sNetlinkFd;
+        aContext->mMaxFd = sNetlinkFd;
     }
 
 #if OPENTHREAD_POSIX_USE_MLD_MONITOR
-    if (sMLDMonitorFd > *aMaxFd)
+    if (sMLDMonitorFd > aContext->mMaxFd)
     {
-        *aMaxFd = sMLDMonitorFd;
+        aContext->mMaxFd = sMLDMonitorFd;
     }
 #endif
 exit:
     return;
 }
 
-void platformNetifProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, const fd_set *aErrorFdSet)
+void platformNetifProcess(const otSysMainloopContext *aContext)
 {
-    OT_UNUSED_VARIABLE(aWriteFdSet);
+    assert(aContext != nullptr);
     VerifyOrExit(gNetifIndex > 0);
 
-    if (FD_ISSET(sTunFd, aErrorFdSet))
+    if (FD_ISSET(sTunFd, &aContext->mErrorFdSet))
     {
         close(sTunFd);
         DieNow(OT_EXIT_FAILURE);
     }
 
-    if (FD_ISSET(sNetlinkFd, aErrorFdSet))
+    if (FD_ISSET(sNetlinkFd, &aContext->mErrorFdSet))
     {
         close(sNetlinkFd);
         DieNow(OT_EXIT_FAILURE);
     }
 
 #if OPENTHREAD_POSIX_USE_MLD_MONITOR
-    if (FD_ISSET(sMLDMonitorFd, aErrorFdSet))
+    if (FD_ISSET(sMLDMonitorFd, &aContext->mErrorFdSet))
     {
         close(sMLDMonitorFd);
         DieNow(OT_EXIT_FAILURE);
     }
 #endif
 
-    if (FD_ISSET(sTunFd, aReadFdSet))
+    if (FD_ISSET(sTunFd, &aContext->mReadFdSet))
     {
         processTransmit(gInstance);
     }
 
-    if (FD_ISSET(sNetlinkFd, aReadFdSet))
+    if (FD_ISSET(sNetlinkFd, &aContext->mReadFdSet))
     {
         processNetlinkEvent(gInstance);
     }
 
 #if OPENTHREAD_POSIX_USE_MLD_MONITOR
-    if (FD_ISSET(sMLDMonitorFd, aReadFdSet))
+    if (FD_ISSET(sMLDMonitorFd, &aContext->mReadFdSet))
     {
         processMLDEvent(gInstance);
     }
 #endif
 
 #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
-    gResolver.Process(aReadFdSet, aErrorFdSet);
+    gResolver.Process(*aContext);
 #endif
 
 exit:
diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h
index 8c36122..3f76dcd 100644
--- a/src/posix/platform/platform-posix.h
+++ b/src/posix/platform/platform-posix.h
@@ -94,12 +94,6 @@
     uint8_t  mData[OT_EVENT_DATA_MAX_SIZE];
 } OT_TOOL_PACKED_END;
 
-struct RadioProcessContext
-{
-    const fd_set *mReadFdSet;
-    const fd_set *mWriteFdSet;
-};
-
 /**
  * This function initializes the alarm service used by OpenThread.
  *
@@ -184,23 +178,18 @@
 /**
  * This function updates the file descriptor sets with file descriptors used by the radio driver.
  *
- * @param[in,out]  aReadFdSet   A pointer to the read file descriptors.
- * @param[in,out]  aWriteFdSet  A pointer to the write file descriptors.
- * @param[in,out]  aMaxFd       A pointer to the max file descriptor.
- * @param[in,out]  aTimeout     A pointer to the timeout.
+ * @param[in]   aContext    A pointer to the mainloop context.
  *
  */
-void platformRadioUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout);
+void platformRadioUpdateFdSet(otSysMainloopContext *aContext);
 
 /**
  * This function performs radio driver processing.
  *
- * @param[in]   aInstance       A pointer to the OpenThread instance.
- * @param[in]   aReadFdSet      A pointer to the read file descriptors.
- * @param[in]   aWriteFdSet     A pointer to the write file descriptors.
+ * @param[in]   aContext    A pointer to the mainloop context.
  *
  */
-void platformRadioProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet);
+void platformRadioProcess(otInstance *aInstance, const otSysMainloopContext *aContext);
 
 /**
  * This function initializes the random number service used by OpenThread.
@@ -219,22 +208,18 @@
 /**
  * This function updates the file descriptor sets with file descriptors used by the UART driver.
  *
- * @param[in,out]  aReadFdSet   A pointer to the read file descriptors.
- * @param[in,out]  aWriteFdSet  A pointer to the write file descriptors.
- * @param[in,out]  aMaxFd       A pointer to the max file descriptor.
+ * @param[in]   aContext    A pointer to the mainloop context.
  *
  */
-void platformUartUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int *aMaxFd);
+void platformUartUpdateFdSet(otSysMainloopContext *aContext);
 
 /**
  * This function performs radio driver processing.
  *
- * @param[in]   aReadFdSet      A pointer to the read file descriptors.
- * @param[in]   aWriteFdSet     A pointer to the write file descriptors.
- * @param[in]   aErrorFdSet     A pointer to the error file descriptors.
+ * @param[in]   aContext    A pointer to the mainloop context.
  *
  */
-void platformUartProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, const fd_set *aErrorFdSet);
+void platformUartProcess(const otSysMainloopContext *aContext);
 
 /**
  * This function initializes platform netif.
@@ -275,23 +260,18 @@
 /**
  * This function updates the file descriptor sets with file descriptors used by platform netif module.
  *
- * @param[in,out]  aReadFdSet    A pointer to the read file descriptors.
- * @param[in,out]  aWriteFdSet   A pointer to the write file descriptors.
- * @param[in,out]  aErrorFdSet   A pointer to the error file descriptors.
- * @param[in,out]  aMaxFd        A pointer to the max file descriptor.
+ * @param[in,out]  aContext  A pointer to the mainloop context.
  *
  */
-void platformNetifUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int *aMaxFd);
+void platformNetifUpdateFdSet(otSysMainloopContext *aContext);
 
 /**
  * This function performs platform netif processing.
  *
- * @param[in]   aReadFdSet      A pointer to the read file descriptors.
- * @param[in]   aWriteFdSet     A pointer to the write file descriptors.
- * @param[in]   aErrorFdSet     A pointer to the error file descriptors.
+ * @param[in]  aContext  A pointer to the mainloop context.
  *
  */
-void platformNetifProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, const fd_set *aErrorFdSet);
+void platformNetifProcess(const otSysMainloopContext *aContext);
 
 /**
  * This function performs notifies state changes to platform netif.
@@ -319,32 +299,19 @@
 /**
  * This function performs virtual time simulation processing.
  *
- * @param[in]   aInstance       A pointer to the OpenThread instance.
- * @param[in]   aReadFdSet      A pointer to the read file descriptors.
- * @param[in]   aWriteFdSet     A pointer to the write file descriptors.
+ * @param[in]  aContext  A pointer to the mainloop context.
  *
  */
-void virtualTimeProcess(otInstance   *aInstance,
-                        const fd_set *aReadFdSet,
-                        const fd_set *aWriteFdSet,
-                        const fd_set *aErrorFdSet);
+void virtualTimeProcess(otInstance *aInstance, const otSysMainloopContext *aContext);
 
 /**
  * This function updates the file descriptor sets with file descriptors
  * used by the virtual time simulation.
  *
- * @param[in,out]  aReadFdSet   A pointer to the read file descriptors.
- * @param[in,out]  aWriteFdSet  A pointer to the write file descriptors.
- * @param[in,out]  aErrorFdSet  A pointer to the error file descriptors.
- * @param[in,out]  aMaxFd       A pointer to the max file descriptor.
- * @param[in,out]  aTimeout     A pointer to the timeout.
+ * @param[in,out]  aContext  A pointer to the mainloop context.
  *
  */
-void virtualTimeUpdateFdSet(fd_set         *aReadFdSet,
-                            fd_set         *aWriteFdSet,
-                            fd_set         *aErrorFdSet,
-                            int            *aMaxFd,
-                            struct timeval *aTimeout);
+void virtualTimeUpdateFdSet(otSysMainloopContext *aContext);
 
 /**
  * This function sends radio spinel event of virtual time simulation.
@@ -403,23 +370,18 @@
 /**
  * This function updates the file descriptor sets with file descriptors used by the TREL driver.
  *
- * @param[in,out]  aReadFdSet   A pointer to the read file descriptors.
- * @param[in,out]  aWriteFdSet  A pointer to the write file descriptors.
- * @param[in,out]  aMaxFd       A pointer to the max file descriptor.
- * @param[in,out]  aTimeout     A pointer to the timeout.
+ * @param[in,out]  aContext  A pointer to the mainloop context.
  *
  */
-void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout);
+void platformTrelUpdateFdSet(otSysMainloopContext *aContext);
 
 /**
  * This function performs TREL driver processing.
  *
- * @param[in]   aInstance       A pointer to the OpenThread instance.
- * @param[in]   aReadFdSet      A pointer to the read file descriptors.
- * @param[in]   aWriteFdSet     A pointer to the write file descriptors.
+ * @param[in]  aContext  A pointer to the mainloop context.
  *
  */
-void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet);
+void platformTrelProcess(otInstance *aInstance, const otSysMainloopContext *aContext);
 
 /**
  * This function creates a socket with SOCK_CLOEXEC flag set.
diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp
index 00e2c1c..17d9c8a 100644
--- a/src/posix/platform/radio.cpp
+++ b/src/posix/platform/radio.cpp
@@ -46,19 +46,15 @@
 #if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
 #include "hdlc_interface.hpp"
 
-#if OPENTHREAD_POSIX_VIRTUAL_TIME
-static ot::Spinel::RadioSpinel<ot::Posix::HdlcInterface, VirtualTimeEvent> sRadioSpinel;
-#else
-static ot::Spinel::RadioSpinel<ot::Posix::HdlcInterface, RadioProcessContext> sRadioSpinel;
-#endif // OPENTHREAD_POSIX_VIRTUAL_TIME
+static ot::Spinel::RadioSpinel<ot::Posix::HdlcInterface> sRadioSpinel;
 #elif OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI
 #include "spi_interface.hpp"
 
-static ot::Spinel::RadioSpinel<ot::Posix::SpiInterface, RadioProcessContext> sRadioSpinel;
+static ot::Spinel::RadioSpinel<ot::Posix::SpiInterface> sRadioSpinel;
 #elif OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_VENDOR
 #include "vendor_interface.hpp"
 
-static ot::Spinel::RadioSpinel<ot::Posix::VendorInterface, RadioProcessContext> sRadioSpinel;
+static ot::Spinel::RadioSpinel<ot::Posix::VendorInterface> sRadioSpinel;
 #else
 #error "OPENTHREAD_POSIX_CONFIG_RCP_BUS only allows OT_POSIX_RCP_BUS_UART, OT_POSIX_RCP_BUS_SPI and " \
     "OT_POSIX_RCP_BUS_VENDOR!"
@@ -313,7 +309,7 @@
     return sRadioSpinel.IsPromiscuous();
 }
 
-void platformRadioUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout)
+void platformRadioUpdateFdSet(otSysMainloopContext *aContext)
 {
     uint64_t now      = otPlatTimeGet();
     uint64_t deadline = sRadioSpinel.GetNextRadioTimeRecalcStart();
@@ -332,24 +328,25 @@
     {
         uint64_t remain = deadline - now;
 
-        if (remain < (static_cast<uint64_t>(aTimeout->tv_sec) * US_PER_S + static_cast<uint64_t>(aTimeout->tv_usec)))
+        if (remain < (static_cast<uint64_t>(aContext->mTimeout.tv_sec) * US_PER_S +
+                      static_cast<uint64_t>(aContext->mTimeout.tv_usec)))
         {
-            aTimeout->tv_sec  = static_cast<time_t>(remain / US_PER_S);
-            aTimeout->tv_usec = static_cast<suseconds_t>(remain % US_PER_S);
+            aContext->mTimeout.tv_sec  = static_cast<time_t>(remain / US_PER_S);
+            aContext->mTimeout.tv_usec = static_cast<suseconds_t>(remain % US_PER_S);
         }
     }
     else
     {
-        aTimeout->tv_sec  = 0;
-        aTimeout->tv_usec = 0;
+        aContext->mTimeout.tv_sec  = 0;
+        aContext->mTimeout.tv_usec = 0;
     }
 
-    sRadioSpinel.GetSpinelInterface().UpdateFdSet(*aReadFdSet, *aWriteFdSet, *aMaxFd, *aTimeout);
+    sRadioSpinel.GetSpinelInterface().UpdateFdSet(aContext);
 
     if (sRadioSpinel.HasPendingFrame() || sRadioSpinel.IsTransmitDone())
     {
-        aTimeout->tv_sec  = 0;
-        aTimeout->tv_usec = 0;
+        aContext->mTimeout.tv_sec  = 0;
+        aContext->mTimeout.tv_usec = 0;
     }
 }
 
@@ -357,15 +354,14 @@
 void virtualTimeRadioSpinelProcess(otInstance *aInstance, const struct VirtualTimeEvent *aEvent)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    sRadioSpinel.Process(*aEvent);
+    sRadioSpinel.Process(aEvent);
 }
 #else
-void platformRadioProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
+void platformRadioProcess(otInstance *aInstance, const otSysMainloopContext *aContext)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    RadioProcessContext context = {aReadFdSet, aWriteFdSet};
 
-    sRadioSpinel.Process(context);
+    sRadioSpinel.Process(aContext);
 }
 #endif // OPENTHREAD_POSIX_VIRTUAL_TIME
 
diff --git a/src/posix/platform/resolver.cpp b/src/posix/platform/resolver.cpp
index 85eda40..c8d80e2 100644
--- a/src/posix/platform/resolver.cpp
+++ b/src/posix/platform/resolver.cpp
@@ -257,30 +257,30 @@
     aTxn->mThreadTxn = nullptr;
 }
 
-void Resolver::UpdateFdSet(fd_set *aReadFdSet, fd_set *aErrorFdSet, int *aMaxFd)
+void Resolver::UpdateFdSet(otSysMainloopContext &aContext)
 {
     for (Transaction &txn : mUpstreamTransaction)
     {
         if (txn.mThreadTxn != nullptr)
         {
-            FD_SET(txn.mUdpFd, aReadFdSet);
-            FD_SET(txn.mUdpFd, aErrorFdSet);
-            if (txn.mUdpFd > *aMaxFd)
+            FD_SET(txn.mUdpFd, &aContext.mReadFdSet);
+            FD_SET(txn.mUdpFd, &aContext.mErrorFdSet);
+            if (txn.mUdpFd > aContext.mMaxFd)
             {
-                *aMaxFd = txn.mUdpFd;
+                aContext.mMaxFd = txn.mUdpFd;
             }
         }
     }
 }
 
-void Resolver::Process(const fd_set *aReadFdSet, const fd_set *aErrorFdSet)
+void Resolver::Process(const otSysMainloopContext &aContext)
 {
     for (Transaction &txn : mUpstreamTransaction)
     {
         if (txn.mThreadTxn != nullptr)
         {
             // Note: On Linux, we can only get the error via read, so they should share the same logic.
-            if (FD_ISSET(txn.mUdpFd, aErrorFdSet) || FD_ISSET(txn.mUdpFd, aReadFdSet))
+            if (FD_ISSET(txn.mUdpFd, &aContext.mErrorFdSet) || FD_ISSET(txn.mUdpFd, &aContext.mReadFdSet))
             {
                 ForwardResponse(&txn);
                 CloseTransaction(&txn);
diff --git a/src/posix/platform/resolver.hpp b/src/posix/platform/resolver.hpp
index ea7da3b..4c50b08 100644
--- a/src/posix/platform/resolver.hpp
+++ b/src/posix/platform/resolver.hpp
@@ -29,6 +29,7 @@
 #ifndef POSIX_PLATFORM_RESOLVER_HPP_
 #define POSIX_PLATFORM_RESOLVER_HPP_
 
+#include <openthread/openthread-system.h>
 #include <openthread/platform/dns.h>
 
 #include <arpa/inet.h>
@@ -78,7 +79,7 @@
      * @param[in,out]  aTimeout     A reference to the timeout.
      *
      */
-    void UpdateFdSet(fd_set *aReadFdSet, fd_set *aErrorFdSet, int *aMaxFd);
+    void UpdateFdSet(otSysMainloopContext &aContext);
 
     /**
      * Handles the result of select.
@@ -87,7 +88,7 @@
      * @param[in]  aErrorFdSet  A reference to the error file descriptors.
      *
      */
-    void Process(const fd_set *aReadFdSet, const fd_set *aErrorFdSet);
+    void Process(const otSysMainloopContext &aContext);
 
 private:
     static constexpr uint64_t kDnsServerListNullCacheTimeoutMs = 1 * 60 * 1000;  // 1 minute
diff --git a/src/posix/platform/spi_interface.cpp b/src/posix/platform/spi_interface.cpp
index d3bf52e..ccf366e 100644
--- a/src/posix/platform/spi_interface.cpp
+++ b/src/posix/platform/spi_interface.cpp
@@ -629,12 +629,13 @@
     return (mIntGpioValueFd >= 0) ? (GetGpioValue(mIntGpioValueFd) == kGpioIntAssertState) : true;
 }
 
-void SpiInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
+void SpiInterface::UpdateFdSet(void *aMainloopContext)
 {
-    struct timeval timeout        = {kSecPerDay, 0};
-    struct timeval pollingTimeout = {0, kSpiPollPeriodUs};
+    struct timeval        timeout        = {kSecPerDay, 0};
+    struct timeval        pollingTimeout = {0, kSpiPollPeriodUs};
+    otSysMainloopContext *context        = reinterpret_cast<otSysMainloopContext *>(aMainloopContext);
 
-    OT_UNUSED_VARIABLE(aWriteFdSet);
+    assert(context != nullptr);
 
     if (mSpiTxIsReady)
     {
@@ -645,9 +646,9 @@
 
     if (mIntGpioValueFd >= 0)
     {
-        if (aMaxFd < mIntGpioValueFd)
+        if (context->mMaxFd < mIntGpioValueFd)
         {
-            aMaxFd = mIntGpioValueFd;
+            context->mMaxFd = mIntGpioValueFd;
         }
 
         if (CheckInterrupt())
@@ -661,7 +662,7 @@
         {
             // The interrupt pin was not asserted, so we wait for the interrupt pin to be asserted by adding it to the
             // read set.
-            FD_SET(mIntGpioValueFd, &aReadFdSet);
+            FD_SET(mIntGpioValueFd, &context->mReadFdSet);
         }
     }
     else if (timercmp(&pollingTimeout, &timeout, <))
@@ -721,19 +722,19 @@
         mDidPrintRateLimitLog = false;
     }
 
-    if (timercmp(&timeout, &aTimeout, <))
+    if (timercmp(&timeout, &context->mTimeout, <))
     {
-        aTimeout = timeout;
+        context->mTimeout = timeout;
     }
 }
 
-void SpiInterface::Process(const RadioProcessContext &aContext) { Process(aContext.mReadFdSet, aContext.mWriteFdSet); }
-
-void SpiInterface::Process(const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
+void SpiInterface::Process(const void *aMainloopContext)
 {
-    OT_UNUSED_VARIABLE(aWriteFdSet);
+    const otSysMainloopContext *context = reinterpret_cast<const otSysMainloopContext *>(aMainloopContext);
 
-    if (FD_ISSET(mIntGpioValueFd, aReadFdSet))
+    assert(context != nullptr);
+
+    if (FD_ISSET(mIntGpioValueFd, &context->mReadFdSet))
     {
         struct gpioevent_data event;
 
@@ -761,25 +762,23 @@
 
     while (now < end)
     {
-        fd_set         readFdSet;
-        fd_set         writeFdSet;
-        int            maxFds = -1;
-        struct timeval timeout;
-        int            ret;
+        otSysMainloopContext context;
+        int                  ret;
 
-        timeout.tv_sec  = static_cast<time_t>((end - now) / US_PER_S);
-        timeout.tv_usec = static_cast<suseconds_t>((end - now) % US_PER_S);
+        context.mMaxFd           = -1;
+        context.mTimeout.tv_sec  = static_cast<time_t>((end - now) / US_PER_S);
+        context.mTimeout.tv_usec = static_cast<suseconds_t>((end - now) % US_PER_S);
 
-        FD_ZERO(&readFdSet);
-        FD_ZERO(&writeFdSet);
+        FD_ZERO(&context.mReadFdSet);
+        FD_ZERO(&context.mWriteFdSet);
 
-        UpdateFdSet(readFdSet, writeFdSet, maxFds, timeout);
+        UpdateFdSet(&context);
 
-        ret = select(maxFds + 1, &readFdSet, &writeFdSet, nullptr, &timeout);
+        ret = select(context.mMaxFd + 1, &context.mReadFdSet, &context.mWriteFdSet, nullptr, &context.mTimeout);
 
         if (ret >= 0)
         {
-            Process(&readFdSet, &writeFdSet);
+            Process(&context);
 
             if (mDidRxFrame)
             {
diff --git a/src/posix/platform/spi_interface.hpp b/src/posix/platform/spi_interface.hpp
index fc7bd3b..549b0c4 100644
--- a/src/posix/platform/spi_interface.hpp
+++ b/src/posix/platform/spi_interface.hpp
@@ -120,21 +120,18 @@
     /**
      * This method updates the file descriptor sets with file descriptors used by the radio driver.
      *
-     * @param[in,out]  aReadFdSet   A reference to the read file descriptors.
-     * @param[in,out]  aWriteFdSet  A reference to the write file descriptors.
-     * @param[in,out]  aMaxFd       A reference to the max file descriptor.
-     * @param[in,out]  aTimeout     A reference to the timeout.
+     * @param[in,out]   aMainloopContext  A pointer to the mainloop context containing fd_sets.
      *
      */
-    void UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout);
+    void UpdateFdSet(void *aMainloopContext);
 
     /**
      * This method performs radio driver processing.
      *
-     * @param[in]   aContext        The context containing fd_sets.
+     * @param[in]   aMainloopContext  A pointer to the mainloop context containing fd_sets.
      *
      */
-    void Process(const RadioProcessContext &aContext);
+    void Process(const void *aMainloopContext);
 
     /**
      * This method returns the bus speed between the host and the radio.
@@ -176,7 +173,6 @@
     uint8_t *GetRealRxFrameStart(uint8_t *aSpiRxFrameBuffer, uint8_t aAlignAllowance, uint16_t &aSkipLength);
     otError  DoSpiTransfer(uint8_t *aSpiRxFrameBuffer, uint32_t aTransferLength);
     otError  PushPullSpi(void);
-    void     Process(const fd_set *aReadFdSet, const fd_set *aWriteFdSet);
 
     bool CheckInterrupt(void);
     void LogStats(void);
diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp
index 6f11db7..3438839 100644
--- a/src/posix/platform/system.cpp
+++ b/src/posix/platform/system.cpp
@@ -292,29 +292,26 @@
 /**
  * This function try selecting the given file descriptors in nonblocking mode.
  *
- * @param[in,out]   aReadFdSet   A pointer to the read file descriptors.
- * @param[in,out]   aWriteFdSet  A pointer to the write file descriptors.
- * @param[in,out]   aErrorFdSet  A pointer to the error file descriptors.
- * @param[in]       aMaxFd       The max file descriptor.
+ * @param[in,out]  aContext  A reference to the mainloop context.
  *
  * @returns The value returned from select().
  *
  */
-static int trySelect(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int aMaxFd)
+static int trySelect(otSysMainloopContext &aContext)
 {
     struct timeval timeout          = {0, 0};
-    fd_set         originReadFdSet  = *aReadFdSet;
-    fd_set         originWriteFdSet = *aWriteFdSet;
-    fd_set         originErrorFdSet = *aErrorFdSet;
+    fd_set         originReadFdSet  = aContext.mReadFdSet;
+    fd_set         originWriteFdSet = aContext.mWriteFdSet;
+    fd_set         originErrorFdSet = aContext.mErrorFdSet;
     int            rval;
 
-    rval = select(aMaxFd + 1, aReadFdSet, aWriteFdSet, aErrorFdSet, &timeout);
+    rval = select(aContext.mMaxFd + 1, &aContext.mReadFdSet, &aContext.mWriteFdSet, &aContext.mErrorFdSet, &timeout);
 
     if (rval == 0)
     {
-        *aReadFdSet  = originReadFdSet;
-        *aWriteFdSet = originWriteFdSet;
-        *aErrorFdSet = originErrorFdSet;
+        aContext.mReadFdSet  = originReadFdSet;
+        aContext.mWriteFdSet = originWriteFdSet;
+        aContext.mErrorFdSet = originErrorFdSet;
     }
 
     return rval;
@@ -327,17 +324,15 @@
 
     platformAlarmUpdateTimeout(&aMainloop->mTimeout);
 #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
-    platformNetifUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet,
-                             &aMainloop->mMaxFd);
+    platformNetifUpdateFdSet(aMainloop);
 #endif
 #if OPENTHREAD_POSIX_VIRTUAL_TIME
-    virtualTimeUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet, &aMainloop->mMaxFd,
-                           &aMainloop->mTimeout);
+    virtualTimeUpdateFdSet(aMainloop);
 #else
-    platformRadioUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout);
+    platformRadioUpdateFdSet(aMainloop);
 #endif
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
-    platformTrelUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout);
+    platformTrelUpdateFdSet(aMainloop);
 #endif
 
     if (otTaskletsArePending(aInstance))
@@ -355,7 +350,7 @@
     if (timerisset(&aMainloop->mTimeout))
     {
         // Make sure there are no data ready in UART
-        rval = trySelect(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet, aMainloop->mMaxFd);
+        rval = trySelect(*aMainloop);
 
         if (rval == 0)
         {
@@ -395,16 +390,16 @@
     ot::Posix::Mainloop::Manager::Get().Process(*aMainloop);
 
 #if OPENTHREAD_POSIX_VIRTUAL_TIME
-    virtualTimeProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet);
+    virtualTimeProcess(aInstance, aMainloop);
 #else
-    platformRadioProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet);
+    platformRadioProcess(aInstance, aMainloop);
 #endif
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
-    platformTrelProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet);
+    platformTrelProcess(aInstance, aMainloop);
 #endif
     platformAlarmProcess(aInstance);
 #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
-    platformNetifProcess(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet);
+    platformNetifProcess(aMainloop);
 #endif
 }
 
diff --git a/src/posix/platform/trel.cpp b/src/posix/platform/trel.cpp
index 4b7cd50..2ddab4b 100644
--- a/src/posix/platform/trel.cpp
+++ b/src/posix/platform/trel.cpp
@@ -388,24 +388,20 @@
     // advertising TREL service after this call.
 }
 
-OT_TOOL_WEAK void trelDnssdUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout)
+OT_TOOL_WEAK void trelDnssdUpdateFdSet(otSysMainloopContext *aContext)
 {
     // This function can be used to update the file descriptor sets
     // by DNS-SD layer (if needed).
 
-    OT_UNUSED_VARIABLE(aReadFdSet);
-    OT_UNUSED_VARIABLE(aWriteFdSet);
-    OT_UNUSED_VARIABLE(aMaxFd);
-    OT_UNUSED_VARIABLE(aTimeout);
+    OT_UNUSED_VARIABLE(aContext);
 }
 
-OT_TOOL_WEAK void trelDnssdProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
+OT_TOOL_WEAK void trelDnssdProcess(otInstance *aInstance, const otSysMainloopContext *aContext)
 {
     // This function performs processing by DNS-SD (if needed).
 
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aReadFdSet);
-    OT_UNUSED_VARIABLE(aWriteFdSet);
+    OT_UNUSED_VARIABLE(aContext);
 }
 
 //---------------------------------------------------------------------------------------------------------------------
@@ -525,45 +521,45 @@
     return;
 }
 
-void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout)
+void platformTrelUpdateFdSet(otSysMainloopContext *aContext)
 {
-    assert((aReadFdSet != NULL) && (aWriteFdSet != NULL) && (aMaxFd != NULL) && (aTimeout != NULL));
+    assert(aContext != nullptr);
 
     VerifyOrExit(sEnabled);
 
-    FD_SET(sSocket, aReadFdSet);
+    FD_SET(sSocket, &aContext->mReadFdSet);
 
-    if (sTxPacketQueueTail != NULL)
+    if (sTxPacketQueueTail != nullptr)
     {
-        FD_SET(sSocket, aWriteFdSet);
+        FD_SET(sSocket, &aContext->mWriteFdSet);
     }
 
-    if (*aMaxFd < sSocket)
+    if (aContext->mMaxFd < sSocket)
     {
-        *aMaxFd = sSocket;
+        aContext->mMaxFd = sSocket;
     }
 
-    trelDnssdUpdateFdSet(aReadFdSet, aWriteFdSet, aMaxFd, aTimeout);
+    trelDnssdUpdateFdSet(aContext);
 
 exit:
     return;
 }
 
-void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
+void platformTrelProcess(otInstance *aInstance, const otSysMainloopContext *aContext)
 {
     VerifyOrExit(sEnabled);
 
-    if (FD_ISSET(sSocket, aWriteFdSet))
+    if (FD_ISSET(sSocket, &aContext->mWriteFdSet))
     {
         SendQueuedPackets();
     }
 
-    if (FD_ISSET(sSocket, aReadFdSet))
+    if (FD_ISSET(sSocket, &aContext->mReadFdSet))
     {
         ReceivePacket(sSocket, aInstance);
     }
 
-    trelDnssdProcess(aInstance, aReadFdSet, aWriteFdSet);
+    trelDnssdProcess(aInstance, aContext);
 
 exit:
     return;
diff --git a/src/posix/platform/vendor_interface.hpp b/src/posix/platform/vendor_interface.hpp
index 6878af5..aecd939 100644
--- a/src/posix/platform/vendor_interface.hpp
+++ b/src/posix/platform/vendor_interface.hpp
@@ -117,21 +117,18 @@
     /**
      * This method updates the file descriptor sets with file descriptors used by the radio driver.
      *
-     * @param[in,out] aReadFdSet   A reference to the read file descriptors.
-     * @param[in,out] aWriteFdSet  A reference to the write file descriptors.
-     * @param[in,out] aMaxFd       A reference to the max file descriptor.
-     * @param[in,out] aTimeout     A reference to the timeout.
+     * @param[in,out]   aMainloopContext  A pointer to the mainloop context containing fd_sets.
      *
      */
-    void UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout);
+    void UpdateFdSet(void *aMainloopContext);
 
     /**
      * This method performs radio driver processing.
      *
-     * @param[in] aContext  The context containing fd_sets.
+     * @param[in]   aMainloopContext  A pointer to the mainloop context containing fd_sets.
      *
      */
-    void Process(const RadioProcessContext &aContext);
+    void Process(const void *aMainloopContext);
 
     /**
      * This method returns the bus speed between the host and the radio.
diff --git a/src/posix/platform/vendor_interface_example.cpp b/src/posix/platform/vendor_interface_example.cpp
index 073b379..f45a932 100644
--- a/src/posix/platform/vendor_interface_example.cpp
+++ b/src/posix/platform/vendor_interface_example.cpp
@@ -108,19 +108,16 @@
     return OT_ERROR_NOT_IMPLEMENTED;
 }
 
-void VendorInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
+void VendorInterface::UpdateFdSet(void *aMainloopContext)
 {
-    OT_UNUSED_VARIABLE(aReadFdSet);
-    OT_UNUSED_VARIABLE(aWriteFdSet);
-    OT_UNUSED_VARIABLE(aMaxFd);
-    OT_UNUSED_VARIABLE(aTimeout);
+    OT_UNUSED_VARIABLE(aMainloopContext);
 
     // TODO: Implement vendor code here.
 }
 
-void VendorInterface::Process(const RadioProcessContext &aContext)
+void VendorInterface::Process(const void *aMainloopContext)
 {
-    OT_UNUSED_VARIABLE(aContext);
+    OT_UNUSED_VARIABLE(aMainloopContext);
 
     // TODO: Implement vendor code here.
 }
diff --git a/src/posix/platform/virtual_time.cpp b/src/posix/platform/virtual_time.cpp
index 963c1d9..e278ed4 100644
--- a/src/posix/platform/virtual_time.cpp
+++ b/src/posix/platform/virtual_time.cpp
@@ -159,37 +159,24 @@
     virtualTimeSendEvent(&event, offsetof(struct VirtualTimeEvent, mData) + event.mDataLength);
 }
 
-void virtualTimeUpdateFdSet(fd_set         *aReadFdSet,
-                            fd_set         *aWriteFdSet,
-                            fd_set         *aErrorFdSet,
-                            int            *aMaxFd,
-                            struct timeval *aTimeout)
+void virtualTimeUpdateFdSet(otSysMainloopContext *aContext)
 {
-    OT_UNUSED_VARIABLE(aWriteFdSet);
-    OT_UNUSED_VARIABLE(aErrorFdSet);
-    OT_UNUSED_VARIABLE(aTimeout);
-
-    FD_SET(sSockFd, aReadFdSet);
-    if (*aMaxFd < sSockFd)
+    FD_SET(sSockFd, &aContext->mReadFdSet);
+    if (aContext->mMaxFd < sSockFd)
     {
-        *aMaxFd = sSockFd;
+        aContext->mMaxFd = sSockFd;
     }
 }
 
-void virtualTimeProcess(otInstance   *aInstance,
-                        const fd_set *aReadFdSet,
-                        const fd_set *aWriteFdSet,
-                        const fd_set *aErrorFdSet)
+void virtualTimeProcess(otInstance *aInstance, const otSysMainloopContext *aContext)
 {
     struct VirtualTimeEvent event;
 
     memset(&event, 0, sizeof(event));
 
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aWriteFdSet);
-    OT_UNUSED_VARIABLE(aErrorFdSet);
 
-    if (FD_ISSET(sSockFd, aReadFdSet))
+    if (FD_ISSET(sSockFd, &aContext->mReadFdSet))
     {
         virtualTimeReceiveEvent(&event);
     }