ADE9113 driver for IMX6ULL#670
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a Phoenix-RTOS driver for the ADE9113 ADC (4 chained chips), featuring command/response handling, IMX6ULL DMA support, a message API server, and a sample ring buffer. It also updates the IMX6ULL SDMA driver to support automatic buffer descriptor completion options and word-aligned memory access. Critical feedback identifies a potential deadlock in sampleRead due to a missing unlock on an error path, a null pointer dereference in the message API when handling inactive sessions, and an incorrect mmap failure check. Other high-to-medium severity issues include a compilation warning from returning a value in a void function, a resource leak in sample_init on allocation failure, a leftover debug condition in the monitor thread, and the need to use static inline void for sample_write to prevent linker issues.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| struct sampleReader *reader = NULL; | ||
| if ((msg.oid.id > 0) && (msg.oid.id <= MSGAPI_READER_COUNT)) { | ||
| reader = &ctx->readers[msg.oid.id - 1]; | ||
| } |
There was a problem hiding this comment.
If a client sends mtRead or mtClose with a valid msg.oid.id but for a session that is not open or already closed, reader->ctx is NULL. Calling sample_readBlock or sample_readerClose on such a reader will dereference NULL and crash the driver. We should validate that the reader is active before processing.
struct sampleReader *reader = NULL;
if ((msg.oid.id > 0) && (msg.oid.id <= MSGAPI_READER_COUNT)) {
reader = &ctx->readers[msg.oid.id - 1];
if (reader->ctx == NULL) {
reader = NULL;
}
}There was a problem hiding this comment.
I added separate sample_readerInit function so that ctx is always valid
| int sample_init(struct sampleCtx *ctx, unsigned expSize, unsigned sampleSize) | ||
| { | ||
| if ((expSize > 31) || (((uint32_t)sampleSize >> expSize) > 0)) { | ||
| return -1; | ||
| } | ||
|
|
||
| *ctx = (struct sampleCtx) { | ||
| .written = 0, | ||
| .sampleSize = sampleSize, | ||
| .mask = ((uint32_t)1 << expSize) - 1, | ||
| .buffer = malloc((uint32_t)1 << expSize) | ||
| }; | ||
|
|
||
| if (mutexCreate(&ctx->lock) < 0) { | ||
| fprintf(stderr, "mutex create failed"); | ||
| assert(false); | ||
| return -1; | ||
| } | ||
| if (condCreate(&ctx->cond) < 0) { | ||
| fprintf(stderr, "cond create failed"); | ||
| assert(false); | ||
| return -1; | ||
| } | ||
|
|
||
| if (ctx->buffer == NULL) { | ||
| fprintf(stderr, "failed to allocate sample buffer"); | ||
| return -1; | ||
| } | ||
| return 0; |
There was a problem hiding this comment.
In sample_init, if malloc fails to allocate ctx->buffer, the function returns -1 but does not destroy the previously created ctx->lock and ctx->cond. Allocating the buffer first avoids this leak entirely.
int sample_init(struct sampleCtx *ctx, unsigned expSize, unsigned sampleSize)
{
if ((expSize > 31) || (((uint32_t)sampleSize >> expSize) > 0)) {
return -1;
}
uint8_t *buffer = malloc((uint32_t)1 << expSize);
if (buffer == NULL) {
fprintf(stderr, "failed to allocate sample buffer");
return -1;
}
*ctx = (struct sampleCtx) {
.written = 0,
.sampleSize = sampleSize,
.mask = ((uint32_t)1 << expSize) - 1,
.buffer = buffer
};
if (mutexCreate(&ctx->lock) < 0) {
fprintf(stderr, "mutex create failed");
free(buffer);
assert(false);
return -1;
}
if (condCreate(&ctx->cond) < 0) {
fprintf(stderr, "cond create failed");
free(buffer);
assert(false);
return -1;
}
return 0;
}63f4fb7 to
e464da1
Compare
f67967c to
6247513
Compare
anglov
left a comment
There was a problem hiding this comment.
Commit with generated headers should be added before implementation
| @@ -0,0 +1,1273 @@ | |||
| /* clang-format off */ | |||
| /* Auto-generated from iomux_enum_defs.xml */ | |||
There was a problem hiding this comment.
Removed, added header. For now added scripts to internal repo.
|
|
||
| unsigned i; | ||
| for (i = 1; i < NUM_OF_SDMA_CHANNELS; i++) { | ||
| sdma_channel_t *channel = &cmn->channel[i]; |
There was a problem hiding this comment.
Refactoring could be done in separate commit (ok as-is)
There was a problem hiding this comment.
Added separate commit
|
|
||
|
|
||
| struct sdmaCtx { | ||
| volatile bool enabled; |
There was a problem hiding this comment.
note: using this flag in interrupt is safe because operation on bool is atomic on arm.
There was a problem hiding this comment.
Changed to sig_atomic_t.
| ade9113_writeRegs(&ade, ADE9113_SYNC_SNAP, ADE9113_SYNC_SNAP_ENC(.align = 1)); | ||
| ade9113_writeRegs(&ade, ADE9113_SYNC_SNAP, ADE9113_SYNC_SNAP_ENC(.snapshot = 1)); |
There was a problem hiding this comment.
Added comment. This sequence is required in datasheet
There was a problem hiding this comment.
Please expand this comment. As described in datasheet doesn't provide much context. Explain what's happening here or cite the relevant part of the datasheet.
| ADE9113_CONFIG0_ENC(.stream_dbg = args->dbgMode, .crc_en_spi_write = 1, .clkout_en = 0)); | ||
|
|
||
| /* scratch values will be checked in DMA responses to ensure there is no shift */ | ||
| ade9113_writeRegsDifferent(&ade, ADE9113_SCRATCH, 0xA0, 0xA1, 0xA2, 0xA3); |
There was a problem hiding this comment.
consider
`SCRATCH_VALUE, SCRATCH_VALUE + 1, SCRATCH_VALUE + 2, SCRATCH_VALUE + 3'
There was a problem hiding this comment.
changed to
/* value written to SCRATCH registers to verify reads (arbitrary unique value for each ADC chip) */
static const uint8_t scratchValues[4] = { 0xa0, 0xa1, 0xa2, 0xa3 };|
|
||
| msgapi_init(&ctx->msgapi, &ctx->samples, "ade9113"); | ||
|
|
||
| static uint8_t msgStacks[2][_PAGE_SIZE] __attribute__((aligned(16))); |
There was a problem hiding this comment.
Changed to 8. Kernel's HAL adds padding if to stack if not padded to 8.
| msg.o.err = -EBUSY; | ||
|
|
||
| if (mutexLock(ctx->lock) < 0) { | ||
| msg.o.err = -ENOSYS; |
There was a problem hiding this comment.
changed to -EIO as there is no better code for internal (non temporary) errors
| @@ -0,0 +1,557 @@ | |||
| /* ADE9113 register and field definitions. */ | |||
| /* Auto-generated from ade9113.svd - do not edit manually. */ | |||
There was a problem hiding this comment.
For now internal devtools repo. Proper way to upstream this and provide headers via library should be considered.
TASK: MSH-36
TASK: MSH-36
SDMA commands GET_DM and SET_DM take size in 32 bit words. TASK: MSH-36
Lock is always released inside condWait TASK: MSH-36
TASK: MSH-36
There is no need to `DSPOVR`o register. IMX6 does not have DSP core. It is set to 0xffffffff after reset and there is no need for any explicit writes from driver. Line `DSPOVR |= 1 << channel_id` looks like it might do something important. It does not. It makes `sdma_init_core` harder to understand without any benefit. TASK: MSH-36
TASK: MSH-36
BD_DONE flags indicate that SDMA "owns" buffer described in BD and can write into it. When SDMA returns ownership to processor/driver buffer content should be read *before* setting BD_DONE flag. This ensures data integrity (no read / write collisions). This description assumes RX transfer, but same applies to TX with read / writes swapped. Automatic BD_DONE set can still be useful in some cases like sending fixed buffer repeatedaly (like read requests from ADC). Backwards incompatible. Use `channelCfg.options = sdma_chOption__auto_bd_done` to keep old behaviour. TASK: MSH-36
TASK: MSH-36
| * | ||
| * SPDX-License-Identifier: BSD-3-Clause | ||
| */ | ||
|
|
There was a problem hiding this comment.
Please add header guard.
| * SPDX-License-Identifier: BSD-3-Clause | ||
| */ | ||
|
|
||
|
|
There was a problem hiding this comment.
Please add header guard.
| */ | ||
|
|
||
|
|
||
| #ifndef MSGAPI_H |
| */ | ||
|
|
||
|
|
||
| #ifndef SAMPLE_H |
|
|
||
| #define log_error(fmt, ...) \ | ||
| do { \ | ||
| fprintf(stdout, LOG_TAG fmt "\n", ##__VA_ARGS__); \ |
| fprintf(stdout, LOG_TAG fmt "\n", ##__VA_ARGS__); \ | ||
| } while (0) | ||
|
|
||
| #define log_info(fmt, ...) log_error(fmt, ##__VA_ARGS__); |
| 0 | ||
| | (0x01 << 23) /* RXDEN = 1 */ | ||
| | (15 << 16) /* RX_THRESHOLD = 15 */ | ||
| | (0x01 << 7) /* TXDEN = 1 */ |
| ade9113_readRegs(&ade, ADE9113_CONFIG_CRC, values, sizeof(values)); | ||
| bool ready = true; | ||
| for (int i = 0; i < 4; ++i) { | ||
| if ((values[i] & 0x01) == 0) { |
There was a problem hiding this comment.
0x1 is CRC_FORCE. According to the datasheet, it's automatically cleared when CRC recalculation completes. Did you mean to check CRC_DONE (0x2) instead?
| ade9113_writeRegs(&ade, ADE9113_SYNC_SNAP, ADE9113_SYNC_SNAP_ENC(.align = 1)); | ||
| ade9113_writeRegs(&ade, ADE9113_SYNC_SNAP, ADE9113_SYNC_SNAP_ENC(.snapshot = 1)); |
There was a problem hiding this comment.
Please expand this comment. As described in datasheet doesn't provide much context. Explain what's happening here or cite the relevant part of the datasheet.
| ADE9113_SWRST__SOFTWARE_RESET_COMMAND, | ||
| ADE9113_SWRST__SOFTWARE_RESET_COMMAND, | ||
| ADE9113_SWRST__SOFTWARE_RESET_COMMAND); | ||
| usleep(300 * 1000); |
There was a problem hiding this comment.
Nitpick: consider polling STATUS0 for RESET_DONE instead of using a fixed usleep.
Driver for continuous sample reading from 4 x ADE9113 connected in SPI chain on IMX6ULL
Description
Motivation and Context
Types of changes
How Has This Been Tested?
Checklist:
Special treatment