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