• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2024, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "spinel_driver.hpp"
30 
31 #include <assert.h>
32 
33 #include <openthread/platform/time.h>
34 
35 #include "common/code_utils.hpp"
36 #include "common/new.hpp"
37 #include "lib/platform/exit_code.h"
38 #include "lib/spinel/spinel.h"
39 #include "lib/utils/math.hpp"
40 
41 namespace ot {
42 namespace Spinel {
43 
44 constexpr spinel_tid_t sTid = 1; ///< In Spinel Driver, only use Tid as 1.
45 
SpinelDriver(void)46 SpinelDriver::SpinelDriver(void)
47     : Logger("SpinelDriver")
48     , mSpinelInterface(nullptr)
49     , mWaitingKey(SPINEL_PROP_LAST_STATUS)
50     , mIsWaitingForResponse(false)
51     , mIid(SPINEL_HEADER_INVALID_IID)
52     , mSpinelVersionMajor(-1)
53     , mSpinelVersionMinor(-1)
54     , mIsCoprocessorReady(false)
55 {
56     memset(mVersion, 0, sizeof(mVersion));
57 
58     mReceivedFrameHandler = &HandleInitialFrame;
59     mFrameHandlerContext  = this;
60 }
61 
Init(SpinelInterface & aSpinelInterface,bool aSoftwareReset,const spinel_iid_t * aIidList,uint8_t aIidListLength)62 CoprocessorType SpinelDriver::Init(SpinelInterface    &aSpinelInterface,
63                                    bool                aSoftwareReset,
64                                    const spinel_iid_t *aIidList,
65                                    uint8_t             aIidListLength)
66 {
67     CoprocessorType coprocessorType;
68 
69     mSpinelInterface = &aSpinelInterface;
70     mRxFrameBuffer.Clear();
71     SuccessOrDie(mSpinelInterface->Init(HandleReceivedFrame, this, mRxFrameBuffer));
72 
73     VerifyOrDie(aIidList != nullptr, OT_EXIT_INVALID_ARGUMENTS);
74     VerifyOrDie(aIidListLength != 0 && aIidListLength <= mIidList.GetMaxSize(), OT_EXIT_INVALID_ARGUMENTS);
75 
76     for (uint8_t i = 0; i < aIidListLength; i++)
77     {
78         SuccessOrDie(mIidList.PushBack(aIidList[i]));
79     }
80     mIid = aIidList[0];
81 
82     ResetCoprocessor(aSoftwareReset);
83     SuccessOrDie(CheckSpinelVersion());
84     SuccessOrDie(GetCoprocessorVersion());
85     SuccessOrDie(GetCoprocessorCaps());
86 
87     coprocessorType = GetCoprocessorType();
88     if (coprocessorType == OT_COPROCESSOR_UNKNOWN)
89     {
90         LogCrit("The coprocessor mode is unknown!");
91         DieNow(OT_EXIT_FAILURE);
92     }
93 
94     return coprocessorType;
95 }
96 
Deinit(void)97 void SpinelDriver::Deinit(void)
98 {
99     // This allows implementing pseudo reset.
100     new (this) SpinelDriver();
101 }
102 
SendReset(uint8_t aResetType)103 otError SpinelDriver::SendReset(uint8_t aResetType)
104 {
105     otError        error = OT_ERROR_NONE;
106     uint8_t        buffer[kMaxSpinelFrame];
107     spinel_ssize_t packed;
108 
109     // Pack the header, command and key
110     packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_COMMAND_S SPINEL_DATATYPE_UINT8_S,
111                                   SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid), SPINEL_CMD_RESET, aResetType);
112 
113     VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
114 
115     SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, static_cast<uint16_t>(packed)));
116     LogSpinelFrame(buffer, static_cast<uint16_t>(packed), true /* aTx */);
117 
118 exit:
119     return error;
120 }
121 
ResetCoprocessor(bool aSoftwareReset)122 void SpinelDriver::ResetCoprocessor(bool aSoftwareReset)
123 {
124     bool hardwareReset;
125     bool resetDone = false;
126 
127 #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
128     // Avoid resetting the device twice in a row in Multipan RCP architecture
129     VerifyOrExit(!mIsCoprocessorReady, resetDone = true);
130 #endif
131 
132     mWaitingKey = SPINEL_PROP_LAST_STATUS;
133 
134     if (aSoftwareReset && (SendReset(SPINEL_RESET_STACK) == OT_ERROR_NONE) && (WaitResponse() == OT_ERROR_NONE))
135     {
136         VerifyOrExit(mIsCoprocessorReady, resetDone = false);
137         LogCrit("Software reset co-processor successfully");
138         ExitNow(resetDone = true);
139     }
140 
141     hardwareReset = (mSpinelInterface->HardwareReset() == OT_ERROR_NONE);
142 
143     if (hardwareReset)
144     {
145         SuccessOrExit(WaitResponse());
146     }
147 
148     resetDone = true;
149 
150     if (hardwareReset)
151     {
152         LogInfo("Hardware reset co-processor successfully");
153     }
154     else
155     {
156         LogInfo("co-processor self reset successfully");
157     }
158 
159 exit:
160     if (!resetDone)
161     {
162         LogCrit("Failed to reset co-processor!");
163         DieNow(OT_EXIT_FAILURE);
164     }
165 }
166 
Process(const void * aContext)167 void SpinelDriver::Process(const void *aContext)
168 {
169     if (mRxFrameBuffer.HasSavedFrame())
170     {
171         ProcessFrameQueue();
172     }
173 
174     mSpinelInterface->Process(aContext);
175 
176     if (mRxFrameBuffer.HasSavedFrame())
177     {
178         ProcessFrameQueue();
179     }
180 }
181 
SendCommand(uint32_t aCommand,spinel_prop_key_t aKey,spinel_tid_t aTid)182 otError SpinelDriver::SendCommand(uint32_t aCommand, spinel_prop_key_t aKey, spinel_tid_t aTid)
183 {
184     otError        error = OT_ERROR_NONE;
185     uint8_t        buffer[kMaxSpinelFrame];
186     spinel_ssize_t packed;
187     uint16_t       offset;
188 
189     // Pack the header, command and key
190     packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid,
191                                   aCommand, aKey);
192 
193     VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
194 
195     offset = static_cast<uint16_t>(packed);
196 
197     SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset));
198     LogSpinelFrame(buffer, offset, true /* aTx */);
199 
200 exit:
201     return error;
202 }
203 
SendCommand(uint32_t aCommand,spinel_prop_key_t aKey,spinel_tid_t aTid,const char * aFormat,va_list aArgs)204 otError SpinelDriver::SendCommand(uint32_t          aCommand,
205                                   spinel_prop_key_t aKey,
206                                   spinel_tid_t      aTid,
207                                   const char       *aFormat,
208                                   va_list           aArgs)
209 {
210     otError        error = OT_ERROR_NONE;
211     uint8_t        buffer[kMaxSpinelFrame];
212     spinel_ssize_t packed;
213     uint16_t       offset;
214 
215     // Pack the header, command and key
216     packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid,
217                                   aCommand, aKey);
218 
219     VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
220 
221     offset = static_cast<uint16_t>(packed);
222 
223     // Pack the data (if any)
224     if (aFormat)
225     {
226         packed = spinel_datatype_vpack(buffer + offset, sizeof(buffer) - offset, aFormat, aArgs);
227         VerifyOrExit(packed > 0 && static_cast<size_t>(packed + offset) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
228 
229         offset += static_cast<uint16_t>(packed);
230     }
231 
232     SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset));
233     LogSpinelFrame(buffer, offset, true /* aTx */);
234 
235 exit:
236     return error;
237 }
238 
SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler,SavedFrameHandler aSavedFrameHandler,void * aContext)239 void SpinelDriver::SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler,
240                                    SavedFrameHandler    aSavedFrameHandler,
241                                    void                *aContext)
242 {
243     mReceivedFrameHandler = aReceivedFrameHandler;
244     mSavedFrameHandler    = aSavedFrameHandler;
245     mFrameHandlerContext  = aContext;
246 }
247 
WaitResponse(void)248 otError SpinelDriver::WaitResponse(void)
249 {
250     otError  error = OT_ERROR_NONE;
251     uint64_t end   = otPlatTimeGet() + kMaxWaitTime * kUsPerMs;
252 
253     LogDebg("Waiting response: key=%lu", Lib::Utils::ToUlong(mWaitingKey));
254 
255     do
256     {
257         uint64_t now = otPlatTimeGet();
258 
259         if ((end <= now) || (mSpinelInterface->WaitForFrame(end - now) != OT_ERROR_NONE))
260         {
261             LogWarn("Wait for response timeout");
262             ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
263         }
264     } while (mIsWaitingForResponse || !mIsCoprocessorReady);
265 
266     mWaitingKey = SPINEL_PROP_LAST_STATUS;
267 
268 exit:
269     return error;
270 }
271 
HandleReceivedFrame(void * aContext)272 void SpinelDriver::HandleReceivedFrame(void *aContext) { static_cast<SpinelDriver *>(aContext)->HandleReceivedFrame(); }
273 
HandleReceivedFrame(void)274 void SpinelDriver::HandleReceivedFrame(void)
275 {
276     otError        error = OT_ERROR_NONE;
277     uint8_t        header;
278     spinel_ssize_t unpacked;
279     bool           shouldSave = true;
280     spinel_iid_t   iid;
281 
282     LogSpinelFrame(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), false);
283     unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header);
284 
285     // Accept spinel messages with the correct IID or broadcast IID.
286     iid = SPINEL_HEADER_GET_IID(header);
287 
288     if (!mIidList.Contains(iid))
289     {
290         mRxFrameBuffer.DiscardFrame();
291         ExitNow();
292     }
293 
294     VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG, error = OT_ERROR_PARSE);
295 
296     assert(mReceivedFrameHandler != nullptr && mFrameHandlerContext != nullptr);
297     mReceivedFrameHandler(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), header, shouldSave,
298                           mFrameHandlerContext);
299 
300     if (shouldSave)
301     {
302         error = mRxFrameBuffer.SaveFrame();
303     }
304     else
305     {
306         mRxFrameBuffer.DiscardFrame();
307     }
308 
309 exit:
310     if (error != OT_ERROR_NONE)
311     {
312         mRxFrameBuffer.DiscardFrame();
313         LogWarn("Error handling spinel frame: %s", otThreadErrorToString(error));
314     }
315 }
316 
HandleInitialFrame(const uint8_t * aFrame,uint16_t aLength,uint8_t aHeader,bool & aSave,void * aContext)317 void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame,
318                                       uint16_t       aLength,
319                                       uint8_t        aHeader,
320                                       bool          &aSave,
321                                       void          *aContext)
322 {
323     static_cast<SpinelDriver *>(aContext)->HandleInitialFrame(aFrame, aLength, aHeader, aSave);
324 }
325 
HandleInitialFrame(const uint8_t * aFrame,uint16_t aLength,uint8_t aHeader,bool & aSave)326 void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave)
327 {
328     spinel_prop_key_t key;
329     uint8_t          *data   = nullptr;
330     spinel_size_t     len    = 0;
331     uint8_t           header = 0;
332     uint32_t          cmd    = 0;
333     spinel_ssize_t    rval   = 0;
334     spinel_ssize_t    unpacked;
335     otError           error = OT_ERROR_NONE;
336 
337     OT_UNUSED_VARIABLE(aHeader);
338 
339     rval = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len);
340     VerifyOrExit(rval > 0 && cmd >= SPINEL_CMD_PROP_VALUE_IS && cmd <= SPINEL_CMD_PROP_VALUE_REMOVED,
341                  error = OT_ERROR_PARSE);
342 
343     VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS, error = OT_ERROR_DROP);
344 
345     if (key == SPINEL_PROP_LAST_STATUS)
346     {
347         spinel_status_t status = SPINEL_STATUS_OK;
348 
349         unpacked = spinel_datatype_unpack(data, len, "i", &status);
350         VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
351 
352         if (status >= SPINEL_STATUS_RESET__BEGIN && status <= SPINEL_STATUS_RESET__END)
353         {
354             // this clear is necessary in case the RCP has sent messages between disable and reset
355             mRxFrameBuffer.Clear();
356 
357             LogInfo("co-processor reset: %s", spinel_status_to_cstr(status));
358             mIsCoprocessorReady = true;
359         }
360         else
361         {
362             LogInfo("co-processor last status: %s", spinel_status_to_cstr(status));
363             ExitNow();
364         }
365     }
366     else
367     {
368         // Drop other frames when the key isn't waiting key.
369         VerifyOrExit(mWaitingKey == key, error = OT_ERROR_DROP);
370 
371         if (key == SPINEL_PROP_PROTOCOL_VERSION)
372         {
373             unpacked = spinel_datatype_unpack(data, len, (SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S),
374                                               &mSpinelVersionMajor, &mSpinelVersionMinor);
375 
376             VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
377         }
378         else if (key == SPINEL_PROP_NCP_VERSION)
379         {
380             unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion));
381 
382             VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
383         }
384         else if (key == SPINEL_PROP_CAPS)
385         {
386             uint8_t        capsBuffer[kCapsBufferSize];
387             spinel_size_t  capsLength = sizeof(capsBuffer);
388             const uint8_t *capsData   = capsBuffer;
389 
390             unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength);
391 
392             VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
393 
394             while (capsLength > 0)
395             {
396                 unsigned int capability;
397 
398                 unpacked = spinel_datatype_unpack(capsData, capsLength, SPINEL_DATATYPE_UINT_PACKED_S, &capability);
399                 VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
400 
401                 SuccessOrExit(error = mCoprocessorCaps.PushBack(capability));
402 
403                 capsData += unpacked;
404                 capsLength -= static_cast<spinel_size_t>(unpacked);
405             }
406         }
407 
408         mIsWaitingForResponse = false;
409     }
410 
411 exit:
412     aSave = false;
413     LogIfFail("Error processing frame", error);
414 }
415 
CheckSpinelVersion(void)416 otError SpinelDriver::CheckSpinelVersion(void)
417 {
418     otError error = OT_ERROR_NONE;
419 
420     SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PROTOCOL_VERSION, sTid));
421     mIsWaitingForResponse = true;
422     mWaitingKey           = SPINEL_PROP_PROTOCOL_VERSION;
423 
424     SuccessOrExit(error = WaitResponse());
425 
426     if ((mSpinelVersionMajor != SPINEL_PROTOCOL_VERSION_THREAD_MAJOR) ||
427         (mSpinelVersionMinor != SPINEL_PROTOCOL_VERSION_THREAD_MINOR))
428     {
429         LogCrit("Spinel version mismatch - Posix:%d.%d, co-processor:%d.%d", SPINEL_PROTOCOL_VERSION_THREAD_MAJOR,
430                 SPINEL_PROTOCOL_VERSION_THREAD_MINOR, mSpinelVersionMajor, mSpinelVersionMinor);
431         DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE);
432     }
433 
434 exit:
435     return error;
436 }
437 
GetCoprocessorVersion(void)438 otError SpinelDriver::GetCoprocessorVersion(void)
439 {
440     otError error = OT_ERROR_NONE;
441 
442     SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_NCP_VERSION, sTid));
443     mIsWaitingForResponse = true;
444     mWaitingKey           = SPINEL_PROP_NCP_VERSION;
445 
446     SuccessOrExit(error = WaitResponse());
447 exit:
448     return error;
449 }
450 
GetCoprocessorCaps(void)451 otError SpinelDriver::GetCoprocessorCaps(void)
452 {
453     otError error = OT_ERROR_NONE;
454 
455     SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS, sTid));
456     mIsWaitingForResponse = true;
457     mWaitingKey           = SPINEL_PROP_CAPS;
458 
459     SuccessOrExit(error = WaitResponse());
460 exit:
461     return error;
462 }
463 
GetCoprocessorType(void)464 CoprocessorType SpinelDriver::GetCoprocessorType(void)
465 {
466     CoprocessorType type = OT_COPROCESSOR_UNKNOWN;
467 
468     if (CoprocessorHasCap(SPINEL_CAP_CONFIG_RADIO))
469     {
470         type = OT_COPROCESSOR_RCP;
471     }
472     else if (CoprocessorHasCap(SPINEL_CAP_CONFIG_FTD) || CoprocessorHasCap(SPINEL_CAP_CONFIG_MTD))
473     {
474         type = OT_COPROCESSOR_NCP;
475     }
476 
477     return type;
478 }
479 
ProcessFrameQueue(void)480 void SpinelDriver::ProcessFrameQueue(void)
481 {
482     uint8_t *frame = nullptr;
483     uint16_t length;
484 
485     assert(mSavedFrameHandler != nullptr && mFrameHandlerContext != nullptr);
486 
487     while (mRxFrameBuffer.GetNextSavedFrame(frame, length) == OT_ERROR_NONE)
488     {
489         mSavedFrameHandler(frame, length, mFrameHandlerContext);
490     }
491 
492     mRxFrameBuffer.ClearSavedFrames();
493 }
494 
495 } // namespace Spinel
496 } // namespace ot
497