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