Dawn Framework 1.0
Universal data acquisition framework for embedded systems
simple.cxx
1// dawn/src/proto/serial/simple.cxx
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#include "dawn/proto/serial/simple.hxx"
7
8#include "dawn/common/poll_loop.hxx"
9
10#include <fcntl.h>
11#include <poll.h>
12#include <sys/stat.h>
13#include <termios.h>
14#include <unistd.h>
15
16#include <cstring>
17
18using namespace dawn;
19
20int CProtoSerial::sendFrame(uint8_t cmd, const uint8_t *payload, size_t len)
21{
22 uint8_t frame[FRAME_MAX_PAYLOAD + FRAME_MIN_LEN];
23 size_t tosend;
24 ssize_t ret;
25 uint16_t crc;
26
27 if (len > FRAME_MAX_PAYLOAD || fd < 0)
28 {
29 return -1;
30 }
31
32 tosend = FRAME_MIN_LEN + len;
33
34 frame[0] = FRAME_SYNC;
35 frame[1] = (uint8_t)(len & 0xFF);
36 frame[2] = (uint8_t)((len >> 8) & 0xFF);
37 frame[3] = cmd;
38
39 if (len > 0)
40 {
41 std::memcpy(&frame[4], payload, len);
42 }
43
44 crc = calculateCrc(&frame[3], 1 + len);
45 frame[4 + len] = (uint8_t)(crc & 0xFF);
46 frame[5 + len] = (uint8_t)((crc >> 8) & 0xFF);
47
48 ret = write(fd, frame, tosend);
49 if (ret < 0 || static_cast<size_t>(ret) != tosend)
50 {
51 DAWNERR(
52 "Serial sendto failed cmd=0x%02x ret=%zd exp=%zu errno=%d\n", cmd, ret, tosend, errno);
53 return ret < 0 ? static_cast<int>(ret) : -EIO;
54 }
55
56 return OK;
57}
58
59void CProtoSerial::thread()
60{
61 struct pollfd fds[1];
62 SPollLoopCallbacks callbacks;
63
64 /* Open the transport here (not in init()) so a not-yet-connected removable
65 * USB serial device waits for the host without blocking descriptor load.
66 */
67
68 if (serialInit() < 0)
69 {
70 workerThread().markThreadFinished();
71 return;
72 }
73
74 std::memset(fds, 0, sizeof(fds));
75
76 fds[0].fd = fd;
77 fds[0].events = POLLIN;
78 parserPos = 0;
79 parserLen = 0;
80 parserState = 0;
81
82 callbacks.beforePoll = CProtoSerial::cbPollBefore;
83 callbacks.afterPoll = CProtoSerial::cbPollAfter;
84 callbacks.onPollReady = CProtoSerial::cbPollOnReady;
85
86 CPollLoopRunner::run(workerThread(), fds, 1, DAWN_POLL_TIMEOUT_MS, callbacks, this);
87}
88
89int CProtoSerial::pollBefore(struct pollfd *pfds, nfds_t nfds)
90{
91 if (pfds == nullptr || nfds == 0)
92 {
93 return -EINVAL;
94 }
95
96 pfds[0].revents = 0;
97 return OK;
98}
99
100void CProtoSerial::pollAfter(int ret)
101{
102 if (ret < 0)
103 {
104 DAWNERR("serial poll failed %d\n", -errno);
105 }
106}
107
108int CProtoSerial::pollOnReady(struct pollfd *pfds, nfds_t nfds, int pollRet)
109{
110 uint8_t byte;
111 ssize_t ret;
112
113 if (pfds == nullptr || nfds == 0 || pollRet <= 0)
114 {
115 return -EINVAL;
116 }
117
118 if ((pfds[0].revents & POLLIN) == 0)
119 {
120 return OK;
121 }
122
123 ret = read(fd, &byte, 1);
124 if (ret <= 0)
125 {
126 usleep(10000);
127 return static_cast<int>(ret);
128 }
129
130 switch (parserState)
131 {
132 case 0:
133 {
134 if (byte == FRAME_SYNC)
135 {
136 rxbuffer[parserPos] = byte;
137 parserPos = 1;
138 parserState = 1;
139 }
140
141 break;
142 }
143
144 case 1:
145 {
146 parserLen = byte;
147 rxbuffer[parserPos] = byte;
148 parserPos = 2;
149 parserState = 2;
150 break;
151 }
152
153 case 2:
154 {
155 parserLen |= (uint16_t)(byte << 8);
156 if (parserLen > FRAME_MAX_PAYLOAD)
157 {
158 parserState = 0;
159 parserPos = 0;
160 return OK;
161 }
162
163 rxbuffer[parserPos] = byte;
164 parserPos = 3;
165 parserState = 3;
166 break;
167 }
168
169 case 3:
170 {
171 rxbuffer[parserPos++] = byte;
172
173 if (parserPos >= (size_t)(FRAME_MIN_LEN + parserLen))
174 {
175 handleFrame(rxbuffer, parserPos);
176 parserState = 0;
177 parserPos = 0;
178 }
179
180 break;
181 }
182 }
183
184 pfds[0].revents = 0;
185 return OK;
186}
187
188int CProtoSerial::cbPollBefore(void *priv, struct pollfd *pfds, nfds_t nfds)
189{
190 CProtoSerial *self;
191
192 self = static_cast<CProtoSerial *>(priv);
193 if (self == nullptr)
194 {
195 return -EINVAL;
196 }
197
198 return self->pollBefore(pfds, nfds);
199}
200
201void CProtoSerial::cbPollAfter(void *priv, struct pollfd *pfds, nfds_t nfds, int ret)
202{
203 CProtoSerial *self;
204
205 (void)pfds;
206 (void)nfds;
207 self = static_cast<CProtoSerial *>(priv);
208 if (self == nullptr)
209 {
210 return;
211 }
212
213 self->pollAfter(ret);
214}
215
216int CProtoSerial::cbPollOnReady(void *priv, struct pollfd *pfds, nfds_t nfds, int pollRet)
217{
218 CProtoSerial *self;
219
220 self = static_cast<CProtoSerial *>(priv);
221 if (self == nullptr)
222 {
223 return -EINVAL;
224 }
225
226 return self->pollOnReady(pfds, nfds, pollRet);
227}
228
229int CProtoSerial::configureDesc(const CDescObject &desc)
230{
231 SObjectCfg::SObjectCfgItem *item = nullptr;
232 size_t offset;
233
234 offset = 0;
235
236 for (size_t i = 0; i < desc.getSize(); i++)
237 {
238 item = desc.objectCfgItemNext(offset);
239
241 {
242 DAWNERR("Unsupported SERIAL config 0x%08" PRIx32 "\n", item->cfgid.v);
243 return -EINVAL;
244 }
245
246 switch (item->cfgid.s.id)
247 {
248 case PROTO_SERIAL_CFG_IOBIND:
249 {
250 for (size_t j = 0; j < item->cfgid.s.size;)
251 {
252 SProtoSerialIOBind *tmp;
253
254 tmp = reinterpret_cast<SProtoSerialIOBind *>(item->data + j);
255
256 allocObject(tmp);
257 j += sizeof(SProtoSerialIOBind) / 4;
258 }
259
260 break;
261 }
262
263 case PROTO_SERIAL_CFG_PATH:
264 {
265 path = reinterpret_cast<const char *>(&item->data);
266 break;
267 }
268
269 case PROTO_SERIAL_CFG_BAUD:
270 {
271 baud = static_cast<uint32_t>(item->data[0]);
272 break;
273 }
274
275 default:
276 {
277 DAWNERR("Unsupported SERIAL config 0x%08" PRIx32 "\n", item->cfgid.v);
278 return -EINVAL;
279 }
280 }
281 }
282
283 return OK;
284}
285
286int CProtoSerial::serialInit()
287{
288 if (!path)
289 {
290 DAWNERR("Serial path not configured\n");
291 return -1;
292 }
293
294 /* A removable USB serial device (e.g. CDC/ACM) returns -ENOTCONN from
295 * open() until the USB host has enumerated it. Wait here for the host to
296 * connect; any other error is fatal. serialInit() runs in the worker
297 * thread, so this waits for usbdev without blocking the rest of the
298 * descriptor load and without any board-specific bring-up delay.
299 */
300
301 for (;;)
302 {
303 fd = open(path, O_RDWR);
304 if (fd >= 0)
305 {
306 break;
307 }
308
309 if (errno != ENOTCONN)
310 {
311 DAWNERR("Failed to open serial port %s: %d\n", path, errno);
312 return -1;
313 }
314
315 if (workerThread().shouldQuit())
316 {
317 return -1;
318 }
319
320 usleep(100000);
321 }
322
323#ifdef CONFIG_SERIAL_TERMIOS
324 {
325 struct termios tio;
326
327 if (tcgetattr(fd, &tio) == 0)
328 {
329 cfmakeraw(&tio);
330 if (tcsetattr(fd, TCSANOW, &tio) < 0)
331 {
332 DAWNWARN("Serial tcsetattr failed for %s\n", path);
333 }
334 }
335 else
336 {
337 DAWNWARN("Serial tcgetattr failed for %s\n", path);
338 }
339 }
340#endif
341
342 DAWNINFO("Serial port initialized: %s (baud=%u)\n", path, baud);
343
344 return 0;
345}
346
347CProtoSerial::~CProtoSerial()
348{
349 deinit();
350}
351
353{
354 int ret;
355
356 ret = configureDesc(getDesc());
357 if (ret != OK)
358 {
359 DAWNERR("Serial configure failed (error %d)\n", ret);
360 return ret;
361 }
362
363 return OK;
364}
365
367{
368 int ret;
369
370 /* Note: the transport device is opened in thread() (see serialInit), so a
371 * removable USB serial device that is not yet connected does not block the
372 * descriptor load here.
373 */
374
375 ret = createBuffers();
376 if (ret < 0)
377 {
378 DAWNERR("failed to create data %d\n", ret);
379 return ret;
380 }
381
382#ifdef CONFIG_DAWN_IO_NOTIFY
383 ret = setupNotifications();
384 if (ret < 0)
385 {
386 DAWNERR("failed to setup notifications %d\n", ret);
387 }
388#endif
389
390 return OK;
391}
392
394{
395#ifdef CONFIG_DAWN_IO_NOTIFY
396 destroyNotifications();
397#endif
398
400
401 if (fd >= 0)
402 {
403 close(fd);
404 fd = -1;
405 }
406
407 return OK;
408}
409
411{
412 int ret;
413
414 ret = startWorkerThread([this]() { thread(); });
415 if (ret < 0)
416 {
417 DAWNERR("failed to start thread %d\n", ret);
418 return ret;
419 }
420
421 DAWNINFO("Serial protocol started\n");
422
423 return 0;
424}
425
427{
428#ifdef CONFIG_DAWN_IO_NOTIFY
429 cleanupNotifications();
430#endif
431
433
434 DAWNINFO("Serial protocol stopped\n");
435
436 return 0;
437}
438
440{
441 return workerThreadRunning();
442}
Descriptor wrapper for individual object configuration.
size_t getSize() const
Get number of configuration items for this object.
SObjectCfg::SObjectCfgItem * objectCfgItemNext(size_t &offset) const
Get config item at current offset and advance past it.
CDescObject & getDesc()
Get descriptor object for this object.
Definition object.cxx:190
static int run(CThreadedObject &threadCtl, struct pollfd *pfds, nfds_t nfds, int timeoutMs, const SPollLoopCallbacks &callbacks, void *priv)
Run poll loop until quit is requested.
Definition poll_loop.hxx:61
@ PROTO_CLASS_SERIAL
Compact binary protocol over serial port.
Definition common.hxx:63
Simple binary serial protocol for device communication.
Definition simple.hxx:28
int configure()
Configure object from descriptor data.
Definition simple.cxx:352
int init()
One-time initialize object after bindings are resolved.
Definition simple.cxx:366
int doStart()
Start implementation hook.
Definition simple.cxx:410
bool hasThread() const
Check if a background thread is active.
Definition simple.cxx:439
int doStop()
Stop implementation hook.
Definition simple.cxx:426
int deinit()
De-initialize object.
Definition simple.cxx:393
static uint8_t FRAME_SYNC
Frame structure constants.
void allocObject(SProtoSimpleIOBind *cfg)
Store an allocated IO binding.
uint16_t calculateCrc(const uint8_t *data, size_t len)
Calculate 16-bit CRC checksum.
int handleFrame(const uint8_t *frame, size_t len)
Process a received frame.
int createBuffers()
Allocate shared per-IO data buffers.
int destroyBuffers()
Destroy shared per-IO data buffers.
bool workerThreadRunning() const
Check if the worker thread is running.
Definition thread.hxx:269
int stopWorkerThread()
Stop the worker thread.
Definition thread.hxx:258
int startWorkerThread(Func &&func)
Start the worker thread with a given function.
Definition thread.hxx:246
CThreadedObject & workerThread()
Get a reference to this thread controller.
Definition thread.hxx:280
Out-of-tree user-extension hooks for Dawn.
Definition bindable.hxx:13
Single configuration item within object.
ObjectCfgData_t data[]
Configuration data array (flexible, size from cfgid.s.size).
UObjectCfgId cfgid
Configuration ID header (type, class, id, size, rw, dtype).
Callback set for poll-based worker loops.
Definition poll_loop.hxx:28
ObjectCfgId v
Raw 32-bit ConfigID value (for storage, comparison).
Definition objectcfg.hxx:82
uint32_t cls
Object class (bits 21-29, max 511).
uint32_t id
Configuration identifier (bits 0-4, max 31).
Definition objectcfg.hxx:94
uint32_t size
Configuration data size in 32-bit words (bits 5-14, max 1023).
struct dawn::SObjectCfg::UObjectCfgId::@10 s
Bit-field structure for named member access.