1 /*
2 * Copyright (c) 2016, 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" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /**
29 * @file
30 * This file implements a SPI interface to the OpenThread stack.
31 */
32
33 #include "ncp_spi.hpp"
34
35 #include <openthread/ncp.h>
36 #include <openthread/platform/misc.h>
37 #include <openthread/platform/spi-slave.h>
38 #include <openthread/platform/toolchain.h>
39
40 #include "openthread-core-config.h"
41 #include "common/code_utils.hpp"
42 #include "common/debug.hpp"
43 #include "common/new.hpp"
44 #include "instance/instance.hpp"
45 #include "net/ip6.hpp"
46
47 #if OPENTHREAD_CONFIG_NCP_SPI_ENABLE
48
49 #if OPENTHREAD_CONFIG_DIAG_ENABLE
50 static_assert(OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE <=
51 OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE - ot::Ncp::NcpBase::kSpinelCmdHeaderSize -
52 ot::Ncp::NcpBase::kSpinelPropIdSize - ot::Spinel::SpiFrame::kHeaderSize,
53 "diag output should be smaller than NCP SPI tx buffer");
54 static_assert(OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE,
55 "diag command line should be smaller than NCP SPI rx buffer");
56 #endif
57
58 namespace ot {
59 namespace Ncp {
60
61 using Spinel::SpiFrame;
62
63 #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0
64
65 static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpSpi), uint64_t);
66
otNcpSpiInit(otInstance * aInstance)67 extern "C" void otNcpSpiInit(otInstance *aInstance)
68 {
69 NcpSpi *ncpSpi = nullptr;
70 Instance *instance = static_cast<Instance *>(aInstance);
71
72 ncpSpi = new (&sNcpRaw) NcpSpi(instance);
73
74 if (ncpSpi == nullptr || ncpSpi != NcpBase::GetNcpInstance())
75 {
76 OT_ASSERT(false);
77 }
78 }
79
80 #endif // OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0
81
NcpSpi(Instance * aInstance)82 NcpSpi::NcpSpi(Instance *aInstance)
83 : NcpBase(aInstance)
84 , mTxState(kTxStateIdle)
85 , mHandlingRxFrame(false)
86 , mResetFlag(true)
87 , mPrepareTxFrameTask(*aInstance, NcpSpi::PrepareTxFrame)
88 , mSendFrameLength(0)
89 {
90 SpiFrame sendFrame(mSendFrame);
91 SpiFrame emptyFullAccept(mEmptySendFrameFullAccept);
92 SpiFrame emptyZeroAccept(mEmptySendFrameZeroAccept);
93
94 sendFrame.SetHeaderFlagByte(/* aResetFlag */ true);
95 sendFrame.SetHeaderAcceptLen(0);
96 sendFrame.SetHeaderDataLen(0);
97
98 emptyFullAccept.SetHeaderFlagByte(/* aResetFlag */ true);
99 emptyFullAccept.SetHeaderAcceptLen(kSpiBufferSize - kSpiHeaderSize);
100 emptyFullAccept.SetHeaderDataLen(0);
101
102 emptyZeroAccept.SetHeaderFlagByte(/* aResetFlag */ true);
103 emptyZeroAccept.SetHeaderAcceptLen(0);
104 emptyZeroAccept.SetHeaderDataLen(0);
105
106 mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToTxBuffer, this);
107
108 IgnoreError(otPlatSpiSlaveEnable(&NcpSpi::SpiTransactionComplete, &NcpSpi::SpiTransactionProcess, this));
109
110 // We signal an interrupt on this first transaction to
111 // make sure that the host processor knows that our
112 // reset flag was set.
113
114 IgnoreError(otPlatSpiSlavePrepareTransaction(mEmptySendFrameZeroAccept, kSpiHeaderSize, mEmptyReceiveFrame,
115 kSpiHeaderSize,
116 /* aRequestTransactionFlag */ true));
117 }
118
SpiTransactionComplete(void * aContext,uint8_t * aOutputBuf,uint16_t aOutputLen,uint8_t * aInputBuf,uint16_t aInputLen,uint16_t aTransLen)119 bool NcpSpi::SpiTransactionComplete(void *aContext,
120 uint8_t *aOutputBuf,
121 uint16_t aOutputLen,
122 uint8_t *aInputBuf,
123 uint16_t aInputLen,
124 uint16_t aTransLen)
125 {
126 NcpSpi *ncp = reinterpret_cast<NcpSpi *>(aContext);
127
128 return ncp->SpiTransactionComplete(aOutputBuf, aOutputLen, aInputBuf, aInputLen, aTransLen);
129 }
130
SpiTransactionComplete(uint8_t * aOutputBuf,uint16_t aOutputLen,uint8_t * aInputBuf,uint16_t aInputLen,uint16_t aTransLen)131 bool NcpSpi::SpiTransactionComplete(uint8_t *aOutputBuf,
132 uint16_t aOutputLen,
133 uint8_t *aInputBuf,
134 uint16_t aInputLen,
135 uint16_t aTransLen)
136 {
137 // This can be executed from an interrupt context, therefore we cannot
138 // use any of OpenThread APIs here. If further processing is needed,
139 // returned value `shouldProcess` is set to `true` to indicate to
140 // platform SPI slave driver to invoke `SpiTransactionProcess()` callback
141 // which unlike this callback must be called from the same OS context
142 // that OpenThread APIs/callbacks are executed.
143
144 uint16_t transDataLen;
145 bool shouldProcess = false;
146 SpiFrame outputFrame(aOutputBuf);
147 SpiFrame inputFrame(aInputBuf);
148 SpiFrame sendFrame(mSendFrame);
149
150 VerifyOrExit((aTransLen >= kSpiHeaderSize) && (aInputLen >= kSpiHeaderSize) && (aOutputLen >= kSpiHeaderSize));
151 VerifyOrExit(inputFrame.IsValid() && outputFrame.IsValid());
152
153 transDataLen = aTransLen - kSpiHeaderSize;
154
155 if (!mHandlingRxFrame)
156 {
157 uint16_t rxDataLen = inputFrame.GetHeaderDataLen();
158
159 // A new frame is successfully received if input frame
160 // indicates that there is data and the "data len" is not
161 // larger than than the "accept len" we provided in the
162 // exchanged output frame.
163
164 if ((rxDataLen > 0) && (rxDataLen <= transDataLen) && (rxDataLen <= outputFrame.GetHeaderAcceptLen()))
165 {
166 mHandlingRxFrame = true;
167 shouldProcess = true;
168 }
169 }
170
171 if (mTxState == kTxStateSending)
172 {
173 uint16_t txDataLen = outputFrame.GetHeaderDataLen();
174
175 // Frame transmission is successful if master indicates
176 // in the input frame that it could accept the frame
177 // length that was exchanged, i.e., the "data len" in
178 // the output frame is smaller than or equal to "accept
179 // len" in the received input frame from master.
180
181 if ((txDataLen > 0) && (txDataLen <= transDataLen) && (txDataLen <= inputFrame.GetHeaderAcceptLen()))
182 {
183 mTxState = kTxStateHandlingSendDone;
184 shouldProcess = true;
185 }
186 }
187
188 exit:
189 // Determine the input and output frames to prepare a new transaction.
190
191 if (mResetFlag && (aTransLen > 0) && (aOutputLen > 0))
192 {
193 mResetFlag = false;
194 sendFrame.SetHeaderFlagByte(/*aResetFlag */ false);
195 SpiFrame(mEmptySendFrameFullAccept).SetHeaderFlagByte(/*aResetFlag */ false);
196 SpiFrame(mEmptySendFrameZeroAccept).SetHeaderFlagByte(/*aResetFlag */ false);
197 }
198
199 if (mTxState == kTxStateSending)
200 {
201 aOutputBuf = mSendFrame;
202 aOutputLen = mSendFrameLength;
203 }
204 else
205 {
206 aOutputBuf = mHandlingRxFrame ? mEmptySendFrameZeroAccept : mEmptySendFrameFullAccept;
207 aOutputLen = kSpiHeaderSize;
208 }
209
210 if (mHandlingRxFrame)
211 {
212 aInputBuf = mEmptyReceiveFrame;
213 aInputLen = kSpiHeaderSize;
214 }
215 else
216 {
217 aInputBuf = mReceiveFrame;
218 aInputLen = kSpiBufferSize;
219 }
220
221 sendFrame.SetHeaderAcceptLen(aInputLen - kSpiHeaderSize);
222
223 IgnoreError(
224 otPlatSpiSlavePrepareTransaction(aOutputBuf, aOutputLen, aInputBuf, aInputLen, (mTxState == kTxStateSending)));
225
226 return shouldProcess;
227 }
228
SpiTransactionProcess(void * aContext)229 void NcpSpi::SpiTransactionProcess(void *aContext) { reinterpret_cast<NcpSpi *>(aContext)->SpiTransactionProcess(); }
230
SpiTransactionProcess(void)231 void NcpSpi::SpiTransactionProcess(void)
232 {
233 if (mTxState == kTxStateHandlingSendDone)
234 {
235 mPrepareTxFrameTask.Post();
236 }
237
238 if (mHandlingRxFrame)
239 {
240 HandleRxFrame();
241 }
242 }
243
HandleFrameAddedToTxBuffer(void * aContext,Spinel::Buffer::FrameTag aTag,Spinel::Buffer::Priority aPriority,Spinel::Buffer * aBuffer)244 void NcpSpi::HandleFrameAddedToTxBuffer(void *aContext,
245 Spinel::Buffer::FrameTag aTag,
246 Spinel::Buffer::Priority aPriority,
247 Spinel::Buffer *aBuffer)
248 {
249 OT_UNUSED_VARIABLE(aBuffer);
250 OT_UNUSED_VARIABLE(aTag);
251 OT_UNUSED_VARIABLE(aPriority);
252
253 static_cast<NcpSpi *>(aContext)->mPrepareTxFrameTask.Post();
254 }
255
PrepareNextSpiSendFrame(void)256 void NcpSpi::PrepareNextSpiSendFrame(void)
257 {
258 otError error = OT_ERROR_NONE;
259 uint16_t frameLength;
260 uint16_t readLength;
261 SpiFrame sendFrame(mSendFrame);
262
263 VerifyOrExit(!mTxFrameBuffer.IsEmpty());
264
265 if (ShouldWakeHost())
266 {
267 otPlatWakeHost();
268 }
269
270 SuccessOrExit(error = mTxFrameBuffer.OutFrameBegin());
271
272 frameLength = mTxFrameBuffer.OutFrameGetLength();
273 OT_ASSERT(frameLength <= kSpiBufferSize - kSpiHeaderSize);
274
275 // The "accept length" in `mSendFrame` is already updated based
276 // on current state of receive. It is changed either from the
277 // `SpiTransactionComplete()` callback or from `HandleRxFrame()`.
278
279 readLength = mTxFrameBuffer.OutFrameRead(frameLength, sendFrame.GetData());
280 OT_ASSERT(readLength == frameLength);
281
282 // Suppress the warning when assertions are disabled
283 OT_UNUSED_VARIABLE(readLength);
284
285 sendFrame.SetHeaderDataLen(frameLength);
286 mSendFrameLength = frameLength + kSpiHeaderSize;
287
288 mTxState = kTxStateSending;
289
290 // Prepare new transaction by using `mSendFrame` as the output
291 // frame while keeping the input frame unchanged.
292
293 error = otPlatSpiSlavePrepareTransaction(mSendFrame, mSendFrameLength, nullptr, 0, /* aRequestTrans */ true);
294
295 if (error == OT_ERROR_BUSY)
296 {
297 // Being busy is OK. We will get the transaction set up
298 // properly when the current transaction is completed.
299 error = OT_ERROR_NONE;
300 }
301
302 if (error != OT_ERROR_NONE)
303 {
304 mTxState = kTxStateIdle;
305 mPrepareTxFrameTask.Post();
306 ExitNow();
307 }
308
309 IgnoreError(mTxFrameBuffer.OutFrameRemove());
310
311 exit:
312 return;
313 }
314
PrepareTxFrame(Tasklet & aTasklet)315 void NcpSpi::PrepareTxFrame(Tasklet &aTasklet)
316 {
317 OT_UNUSED_VARIABLE(aTasklet);
318 static_cast<NcpSpi *>(GetNcpInstance())->PrepareTxFrame();
319 }
320
PrepareTxFrame(void)321 void NcpSpi::PrepareTxFrame(void)
322 {
323 switch (mTxState)
324 {
325 case kTxStateHandlingSendDone:
326 mTxState = kTxStateIdle;
327
328 OT_FALL_THROUGH;
329 // to next case to prepare the next frame (if any).
330
331 case kTxStateIdle:
332 PrepareNextSpiSendFrame();
333 break;
334
335 case kTxStateSending:
336 // The next frame in queue (if any) will be prepared when the
337 // current frame is successfully sent and this task is posted
338 // again from the `SpiTransactionComplete()` callback.
339 break;
340 }
341 }
342
HandleRxFrame(void)343 void NcpSpi::HandleRxFrame(void)
344 {
345 SpiFrame recvFrame(mReceiveFrame);
346 SpiFrame sendFrame(mSendFrame);
347
348 // Pass the received frame to base class to process.
349 HandleReceive(recvFrame.GetData(), recvFrame.GetHeaderDataLen());
350
351 // The order of operations below is important. We should clear
352 // the `mHandlingRxFrame` before checking `mTxState` and possibly
353 // preparing the next transaction. Note that the callback
354 // `SpiTransactionComplete()` can be invoked from ISR at any point.
355 //
356 // If we switch the order, we have the following race situation:
357 // We check `mTxState` and it is in `kTxStateSending`, so we skip
358 // preparing the transaction here. But before we set the
359 // `mHandlingRxFrame` to `false`, the `SpiTransactionComplete()`
360 // happens and prepares the next transaction and sets the accept
361 // length to zero on `mSendFrame` (since it assumes we are still
362 // handling the previous received frame).
363
364 mHandlingRxFrame = false;
365
366 // If tx state is in `kTxStateSending`, we wait for the callback
367 // `SpiTransactionComplete()` which will then set up everything
368 // and prepare the next transaction.
369
370 if (mTxState != kTxStateSending)
371 {
372 sendFrame.SetHeaderAcceptLen(kSpiBufferSize - kSpiHeaderSize);
373
374 IgnoreError(otPlatSpiSlavePrepareTransaction(mEmptySendFrameFullAccept, kSpiHeaderSize, mReceiveFrame,
375 kSpiBufferSize,
376 /* aRequestTrans */ false));
377
378 // No need to check the error status. Getting `OT_ERROR_BUSY`
379 // is OK as everything will be set up properly from callback when
380 // the current transaction is completed.
381 }
382 }
383
384 } // namespace Ncp
385 } // namespace ot
386
387 #endif // OPENTHREAD_CONFIG_NCP_SPI_ENABLE
388