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     // Avoid resetting the device twice in a row in Multipan RCP architecture
128     VerifyOrExit(!mIsCoprocessorReady, resetDone = true);
129 
130     mWaitingKey = SPINEL_PROP_LAST_STATUS;
131 
132     if (aSoftwareReset && (SendReset(SPINEL_RESET_STACK) == OT_ERROR_NONE) && (WaitResponse() == OT_ERROR_NONE))
133     {
134         VerifyOrExit(mIsCoprocessorReady, resetDone = false);
135         LogCrit("Software reset co-processor successfully");
136         ExitNow(resetDone = true);
137     }
138 
139     hardwareReset = (mSpinelInterface->HardwareReset() == OT_ERROR_NONE);
140 
141     if (hardwareReset)
142     {
143         SuccessOrExit(WaitResponse());
144     }
145 
146     resetDone = true;
147 
148     if (hardwareReset)
149     {
150         LogInfo("Hardware reset co-processor successfully");
151     }
152     else
153     {
154         LogInfo("co-processor self reset successfully");
155     }
156 
157 exit:
158     if (!resetDone)
159     {
160         LogCrit("Failed to reset co-processor!");
161         DieNow(OT_EXIT_FAILURE);
162     }
163 }
164 
Process(const void * aContext)165 void SpinelDriver::Process(const void *aContext)
166 {
167     if (mRxFrameBuffer.HasSavedFrame())
168     {
169         ProcessFrameQueue();
170     }
171 
172     mSpinelInterface->Process(aContext);
173 
174     if (mRxFrameBuffer.HasSavedFrame())
175     {
176         ProcessFrameQueue();
177     }
178 }
179 
SendCommand(uint32_t aCommand,spinel_prop_key_t aKey,spinel_tid_t aTid)180 otError SpinelDriver::SendCommand(uint32_t aCommand, spinel_prop_key_t aKey, spinel_tid_t aTid)
181 {
182     otError        error = OT_ERROR_NONE;
183     uint8_t        buffer[kMaxSpinelFrame];
184     spinel_ssize_t packed;
185     uint16_t       offset;
186 
187     // Pack the header, command and key
188     packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid,
189                                   aCommand, aKey);
190 
191     VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
192 
193     offset = static_cast<uint16_t>(packed);
194 
195     SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset));
196     LogSpinelFrame(buffer, offset, true /* aTx */);
197 
198 exit:
199     return error;
200 }
201 
SendCommand(uint32_t aCommand,spinel_prop_key_t aKey,spinel_tid_t aTid,const char * aFormat,va_list aArgs)202 otError SpinelDriver::SendCommand(uint32_t          aCommand,
203                                   spinel_prop_key_t aKey,
204                                   spinel_tid_t      aTid,
205                                   const char       *aFormat,
206                                   va_list           aArgs)
207 {
208     otError        error = OT_ERROR_NONE;
209     uint8_t        buffer[kMaxSpinelFrame];
210     spinel_ssize_t packed;
211     uint16_t       offset;
212 
213     // Pack the header, command and key
214     packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid,
215                                   aCommand, aKey);
216 
217     VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
218 
219     offset = static_cast<uint16_t>(packed);
220 
221     // Pack the data (if any)
222     if (aFormat)
223     {
224         packed = spinel_datatype_vpack(buffer + offset, sizeof(buffer) - offset, aFormat, aArgs);
225         VerifyOrExit(packed > 0 && static_cast<size_t>(packed + offset) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
226 
227         offset += static_cast<uint16_t>(packed);
228     }
229 
230     SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset));
231     LogSpinelFrame(buffer, offset, true /* aTx */);
232 
233 exit:
234     return error;
235 }
236 
SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler,SavedFrameHandler aSavedFrameHandler,void * aContext)237 void SpinelDriver::SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler,
238                                    SavedFrameHandler    aSavedFrameHandler,
239                                    void                *aContext)
240 {
241     mReceivedFrameHandler = aReceivedFrameHandler;
242     mSavedFrameHandler    = aSavedFrameHandler;
243     mFrameHandlerContext  = aContext;
244 }
245 
WaitResponse(void)246 otError SpinelDriver::WaitResponse(void)
247 {
248     otError  error = OT_ERROR_NONE;
249     uint64_t end   = otPlatTimeGet() + kMaxWaitTime * kUsPerMs;
250 
251     LogDebg("Waiting response: key=%lu", Lib::Utils::ToUlong(mWaitingKey));
252 
253     do
254     {
255         uint64_t now = otPlatTimeGet();
256 
257         if ((end <= now) || (mSpinelInterface->WaitForFrame(end - now) != OT_ERROR_NONE))
258         {
259             LogWarn("Wait for response timeout");
260             ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
261         }
262     } while (mIsWaitingForResponse || !mIsCoprocessorReady);
263 
264     mWaitingKey = SPINEL_PROP_LAST_STATUS;
265 
266 exit:
267     return error;
268 }
269 
HandleReceivedFrame(void * aContext)270 void SpinelDriver::HandleReceivedFrame(void *aContext) { static_cast<SpinelDriver *>(aContext)->HandleReceivedFrame(); }
271 
HandleReceivedFrame(void)272 void SpinelDriver::HandleReceivedFrame(void)
273 {
274     otError        error = OT_ERROR_NONE;
275     uint8_t        header;
276     spinel_ssize_t unpacked;
277     bool           shouldSave = true;
278     spinel_iid_t   iid;
279 
280     LogSpinelFrame(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), false);
281     unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header);
282 
283     // Accept spinel messages with the correct IID or broadcast IID.
284     iid = SPINEL_HEADER_GET_IID(header);
285 
286     if (!mIidList.Contains(iid))
287     {
288         mRxFrameBuffer.DiscardFrame();
289         ExitNow();
290     }
291 
292     VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG, error = OT_ERROR_PARSE);
293 
294     assert(mReceivedFrameHandler != nullptr && mFrameHandlerContext != nullptr);
295     mReceivedFrameHandler(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), header, shouldSave,
296                           mFrameHandlerContext);
297 
298     if (shouldSave)
299     {
300         error = mRxFrameBuffer.SaveFrame();
301     }
302     else
303     {
304         mRxFrameBuffer.DiscardFrame();
305     }
306 
307 exit:
308     if (error != OT_ERROR_NONE)
309     {
310         mRxFrameBuffer.DiscardFrame();
311         LogWarn("Error handling spinel frame: %s", otThreadErrorToString(error));
312     }
313 }
314 
HandleInitialFrame(const uint8_t * aFrame,uint16_t aLength,uint8_t aHeader,bool & aSave,void * aContext)315 void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame,
316                                       uint16_t       aLength,
317                                       uint8_t        aHeader,
318                                       bool          &aSave,
319                                       void          *aContext)
320 {
321     static_cast<SpinelDriver *>(aContext)->HandleInitialFrame(aFrame, aLength, aHeader, aSave);
322 }
323 
HandleInitialFrame(const uint8_t * aFrame,uint16_t aLength,uint8_t aHeader,bool & aSave)324 void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave)
325 {
326     spinel_prop_key_t key;
327     uint8_t          *data   = nullptr;
328     spinel_size_t     len    = 0;
329     uint8_t           header = 0;
330     uint32_t          cmd    = 0;
331     spinel_ssize_t    rval   = 0;
332     spinel_ssize_t    unpacked;
333     otError           error = OT_ERROR_NONE;
334 
335     OT_UNUSED_VARIABLE(aHeader);
336 
337     rval = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len);
338     VerifyOrExit(rval > 0 && cmd >= SPINEL_CMD_PROP_VALUE_IS && cmd <= SPINEL_CMD_PROP_VALUE_REMOVED,
339                  error = OT_ERROR_PARSE);
340 
341     VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS, error = OT_ERROR_DROP);
342 
343     if (key == SPINEL_PROP_LAST_STATUS)
344     {
345         spinel_status_t status = SPINEL_STATUS_OK;
346 
347         unpacked = spinel_datatype_unpack(data, len, "i", &status);
348         VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
349 
350         if (status >= SPINEL_STATUS_RESET__BEGIN && status <= SPINEL_STATUS_RESET__END)
351         {
352             // this clear is necessary in case the RCP has sent messages between disable and reset
353             mRxFrameBuffer.Clear();
354 
355             LogInfo("co-processor reset: %s", spinel_status_to_cstr(status));
356             mIsCoprocessorReady = true;
357         }
358         else
359         {
360             LogInfo("co-processor last status: %s", spinel_status_to_cstr(status));
361             ExitNow();
362         }
363     }
364     else
365     {
366         // Drop other frames when the key isn't waiting key.
367         VerifyOrExit(mWaitingKey == key, error = OT_ERROR_DROP);
368 
369         if (key == SPINEL_PROP_PROTOCOL_VERSION)
370         {
371             unpacked = spinel_datatype_unpack(data, len, (SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S),
372                                               &mSpinelVersionMajor, &mSpinelVersionMinor);
373 
374             VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
375         }
376         else if (key == SPINEL_PROP_NCP_VERSION)
377         {
378             unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion));
379 
380             VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
381         }
382         else if (key == SPINEL_PROP_CAPS)
383         {
384             uint8_t        capsBuffer[kCapsBufferSize];
385             spinel_size_t  capsLength = sizeof(capsBuffer);
386             const uint8_t *capsData   = capsBuffer;
387 
388             unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength);
389 
390             VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
391 
392             while (capsLength > 0)
393             {
394                 unsigned int capability;
395 
396                 unpacked = spinel_datatype_unpack(capsData, capsLength, SPINEL_DATATYPE_UINT_PACKED_S, &capability);
397                 VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
398 
399                 SuccessOrExit(error = mCoprocessorCaps.PushBack(capability));
400 
401                 capsData += unpacked;
402                 capsLength -= static_cast<spinel_size_t>(unpacked);
403             }
404         }
405 
406         mIsWaitingForResponse = false;
407     }
408 
409 exit:
410     aSave = false;
411     LogIfFail("Error processing frame", error);
412 }
413 
CheckSpinelVersion(void)414 otError SpinelDriver::CheckSpinelVersion(void)
415 {
416     otError error = OT_ERROR_NONE;
417 
418     SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PROTOCOL_VERSION, sTid));
419     mIsWaitingForResponse = true;
420     mWaitingKey           = SPINEL_PROP_PROTOCOL_VERSION;
421 
422     SuccessOrExit(error = WaitResponse());
423 
424     if ((mSpinelVersionMajor != SPINEL_PROTOCOL_VERSION_THREAD_MAJOR) ||
425         (mSpinelVersionMinor != SPINEL_PROTOCOL_VERSION_THREAD_MINOR))
426     {
427         LogCrit("Spinel version mismatch - Posix:%d.%d, co-processor:%d.%d", SPINEL_PROTOCOL_VERSION_THREAD_MAJOR,
428                 SPINEL_PROTOCOL_VERSION_THREAD_MINOR, mSpinelVersionMajor, mSpinelVersionMinor);
429         DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE);
430     }
431 
432 exit:
433     return error;
434 }
435 
GetCoprocessorVersion(void)436 otError SpinelDriver::GetCoprocessorVersion(void)
437 {
438     otError error = OT_ERROR_NONE;
439 
440     SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_NCP_VERSION, sTid));
441     mIsWaitingForResponse = true;
442     mWaitingKey           = SPINEL_PROP_NCP_VERSION;
443 
444     SuccessOrExit(error = WaitResponse());
445 exit:
446     return error;
447 }
448 
GetCoprocessorCaps(void)449 otError SpinelDriver::GetCoprocessorCaps(void)
450 {
451     otError error = OT_ERROR_NONE;
452 
453     SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS, sTid));
454     mIsWaitingForResponse = true;
455     mWaitingKey           = SPINEL_PROP_CAPS;
456 
457     SuccessOrExit(error = WaitResponse());
458 exit:
459     return error;
460 }
461 
GetCoprocessorType(void)462 CoprocessorType SpinelDriver::GetCoprocessorType(void)
463 {
464     CoprocessorType type = OT_COPROCESSOR_UNKNOWN;
465 
466     if (CoprocessorHasCap(SPINEL_CAP_CONFIG_RADIO))
467     {
468         type = OT_COPROCESSOR_RCP;
469     }
470     else if (CoprocessorHasCap(SPINEL_CAP_CONFIG_FTD) || CoprocessorHasCap(SPINEL_CAP_CONFIG_MTD))
471     {
472         type = OT_COPROCESSOR_NCP;
473     }
474 
475     return type;
476 }
477 
ProcessFrameQueue(void)478 void SpinelDriver::ProcessFrameQueue(void)
479 {
480     uint8_t *frame = nullptr;
481     uint16_t length;
482 
483     assert(mSavedFrameHandler != nullptr && mFrameHandlerContext != nullptr);
484 
485     while (mRxFrameBuffer.GetNextSavedFrame(frame, length) == OT_ERROR_NONE)
486     {
487         mSavedFrameHandler(frame, length, mFrameHandlerContext);
488     }
489 
490     mRxFrameBuffer.ClearSavedFrames();
491 }
492 
493 } // namespace Spinel
494 } // namespace ot
495