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