MT-158113: imx8mm-evk-spi-transport devicetree overlay - #46
MT-158113: imx8mm-evk-spi-transport devicetree overlay#46MT-MichaelLoh wants to merge 17 commits into
Conversation
…m EVK Kernel port of the SPI transport protocol (firmware-common/spi-transport, vendored from MultiTracksDotCom/firmware @ 9923f343) as a Host-role SPI platform driver, for kernel<->kernel SPI transport testing between the iMX8MM-BB EVK and an STM32F723-DISCO client. Binds to ecspi2 via a new imx8mm-evk-spi-transport.dts overlay, with NSS/NRDY handshake lines owned directly by the driver (see dts comment) rather than SPI-core cs-gpios.
…l builds ccflags-y used -I\$(src)/core/include, which resolves against \$(objtree) under Yocto's O= kernel builds. These vendored core/ headers only exist in the source tree, so every TU failed with "spi_transport/spi_transport.h: No such file or directory". Prefix with $(srctree) to point at the actual source location.
The vendored core also includes these standard hosted-C11 headers, none of which resolve under -nostdinc (this cross-compiler's own freestanding headers aren't on the search path either). Same pattern as the existing string.h shim: redirect to the kernel's own equivalents.
…imeout mt_hw_abort() was a log-only stub, but the core's disconnect watchdog (SPI_TRANSPORT_DISCONNECT_MS, 1500ms) fires before spi_imx_calculate_timeout()'s unconditional >=2000ms floor can possibly elapse. That let a retry reinitialize the shared ctx->msg/ctx->xfer via mt_hw_transfer_start() while spi_imx was still blocked inside its own wait_for_completion_timeout() referencing that same memory -- corrupting the SPI core's message queue and scatterlist state. Reproduced live on the EVK as a NULL deref in spi_imx_dma_transfer()'s sg_last(), triggered by repeated DMA TX timeouts with no peer wired up yet. pAbort() has no return value (must be safe to call whether or not anything is armed, and the core proceeds regardless), so the only correct fix is to make it actually block until the in-flight transfer's completion has fired (or spi-imx's own bounded recovery should have finished), via a completion tracked across mt_hw_transfer_start()/mt_hw_spi_complete()/mt_hw_abort().
mt_transport_event_callback() only logged via dev_dbg(), invisible in dmesg without dynamic debug explicitly enabled -- confirmed live during hardware fault-injection testing against a real STM32F723-DISCO Client: zero log output across ~50 real connect/disconnect cycles and dozens of DMA-failure/timeout injections, even though the callback was firing correctly the whole time. Add per-event atomic counters and a new /sys/.../event_counters attribute, field-named to match the STM32 Client harness's own [DBG] conn=/disc=/hdrCrc=/payCrc=/seq=/dmaFail=/dmaTo= counters (see the firmware repo's test/stm32-disco/app/), so a fault-injection run's peer-side verdict (per that harness's docs/TestPlan.md) can actually be read off this Host instead of only inferred from the absence of a crash.
…module Deletes drivers/spi/spi-mt-transport/ (the driver plus its vendored copy of the SPI transport core library) and the in-tree Kbuild/Kconfig hooks (drivers/spi/Makefile's obj-y line, drivers/spi/Kconfig's CONFIG_SPI_MT_TRANSPORT entry, imx_v8_defconfig's =m line). The driver now builds as an out-of-tree Yocto kernel module, sourced directly from the firmware repo's firmware-common/spi-transport/ (see imx8mmini-bb-evk's meta-mt-transport-evk/recipes-kernel/spi-mt-transport/ spi-mt-transport_git.bb). This removes the real protocol implementation from this more public-facing repo entirely, and removes the vendored-copy duplication (previously tracked via core/PROVENANCE.md) -- the core is now referenced in place from a single source of truth. This branch's only remaining unique content vs. develop is the one devicetree overlay (imx8mm-evk-spi-transport.dts) -- everything else here is the pre-existing, driver-independent v6.6.36-vs-v6.18 kernel-version retarget.
Reverses commit 2b60361's removal for the Linux adapter files only, not the vendored core it also deleted -- CODING_STANDARDS.md §7 in the firmware repo forbids GPL/LGPL code there, and this adapter genuinely calls GPL-only-exported kernel symbols (devm_gpiod_get, gpiod_get_value/set_value/to_irq, spi_async, spi_slave_abort, dev_err_probe, sysfs_emit) core to its own function, so it can't be relicensed -- it has to live somewhere GPL is already the designated home, which per that same policy doc is this repo. The portable protocol core (firmware-common/spi-transport/src/*.c, inc/spi_transport/*.h in the firmware repo) is unaffected and stays put -- it's plain C11, calls no kernel APIs, and doesn't need to be GPL. It's also deliberately NOT vendored back into drivers/spi/ spi-mt-transport/core/ here (that's exactly the duplication problem the original vendored version had) -- core/ is populated at Yocto build time instead, fetched fresh from the firmware repo by imx8mmini-bb-evk's meta-mt-transport-evk linux-imx_%.bbappend, and gitignored here so it never gets committed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
drivers/spi/spi-mt-transport/spi_transport_hw_linux.c:128
- Use gpiod_get_value_cansleep() here since this runs in process context (tick thread) and may be backed by a sleep-capable GPIO provider.
static bool mt_hw_ready_read(void *pContext)
{
struct mt_transport_hw_ctx *ctx = pContext;
return gpiod_get_value(ctx->nrdy_gpiod) ? true : false;
}
drivers/spi/spi-mt-transport/spi_transport_hw_linux.c:109
- This path is not in atomic context (it runs from the protocol tick thread / process context), so using the *_cansleep GPIO accessor is safer for GPIO controllers that may sleep. This avoids potential sleeping-in-atomic bugs if the GPIO descriptor ends up being backed by a sleep-capable provider.
This issue also appears on line 123 of the same file.
static void mt_hw_select_assert(void *pContext, bool high)
{
struct mt_transport_hw_ctx *ctx = pContext;
gpiod_set_value(ctx->nss_gpiod, high ? 1 : 0);
}
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:7
- The file header still says the driver “Builds as an out-of-tree Yocto kernel module”, but it is now wired in-tree under drivers/spi/ (and selected via Kconfig/Makefile). Update this description so it matches the current build/integration model (including the fact that only the portable core is staged at build time).
* SPI transport protocol. Builds as an out-of-tree Yocto kernel module (see
* imx8mmini-bb-evk's meta-mt-transport-evk recipes-kernel/spi-mt-transport/)
* against the portable protocol core at ../../{,../inc/spi_transport}/ in
* this same source tree -- referenced in place, not vendored/copied.
drivers/spi/spi-mt-transport/spi_transport_hw_linux.c:218
- This handler is registered as a threaded IRQ (primary handler is NULL), so it runs in a context where sleeping is allowed. Using gpiod_get_value_cansleep() makes the GPIO read safe even if the GPIO provider can sleep.
irqreturn_t mt_transport_hw_linux_nrdy_irq(int irq, void *dev_id)
{
struct mt_transport_hw_ctx *ctx = dev_id;
bool high = gpiod_get_value(ctx->nrdy_gpiod) ? true : false;
- mt_hw_select_assert()/mt_hw_ready_read()/mt_transport_hw_linux_nrdy_irq() used plain gpiod_get_value()/gpiod_set_value() from non-atomic contexts (tick kthread, and a threaded IRQ handler that can sleep by definition) -- switched to the _cansleep variants, correct per the GPIO consumer API's own contract for sleepable callers, even though this board's actual GPIO controller happens to be non-sleeping today. - Updated spi_mt_transport_drv.c's file banner: still described the driver as an out-of-tree Yocto module with a relative-path core reference and a relative ProtocolSpec.md path, both stale now that it's in-tree here with the core fetched from a separate repo. - Documented the Makefile/defconfig's build-time dependency on the Yocto-staged core/ directory explicitly, so enabling CONFIG_SPI_MT_TRANSPORT outside the imx8mmini-bb-evk BSP pipeline fails with an understood, expected error instead of a cryptic one. Found by Copilot's review of PR MultiTracksDotCom#46 after the adapter moved here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (5)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:335
- This uses '///' (C++-style) comments; prefer kernel-style /* */ comments for in-tree code.
/// @brief True if a new slot can be enqueued. Caller must already hold
/// tx_lock -- occupied total is tx_queued_count (not-yet-submitted
/// slots) plus one more if a slot is currently in flight
/// (tx_in_flight_idx >= 0), since that slot is still reserved even
/// though it doesn't count toward tx_queued_count.
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:351
- This uses '///' (C++-style) comments; prefer kernel-style /* */ comments for in-tree code.
/// @brief wait_event_interruptible()'s condition check only -- takes and
/// releases tx_lock itself since it must be callable without
/// already holding it. mt_transport_misc_write()'s own room check
/// below calls mt_transport_tx_room_locked() directly instead
/// (already holding the lock at that point) rather than this
/// wrapper, and deliberately so: that check has to stay under the
/// *same* lock acquisition that immediately follows (the enqueue),
/// otherwise a second writer could take the now-free slot in the
/// gap between checking and re-locking.
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:554
- gpiod_to_irq() can return -EPROBE_DEFER (or other negative errors). Treating all <=0 values as "no IRQ" can prevent proper probe deferral and permanently disables the IRQ optimization when the IRQ provider isn’t ready yet.
priv->nrdy_irq = gpiod_to_irq(priv->nrdy_gpiod);
if (priv->nrdy_irq > 0) {
ret = devm_request_threaded_irq(dev, priv->nrdy_irq, NULL,
mt_transport_hw_linux_nrdy_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
| IRQF_ONESHOT,
DRIVER_NAME "-nrdy", &priv->hw_ctx);
if (ret)
dev_dbg(dev, "no NRDY IRQ (%d) -- falling back to tick-poll only\n", ret);
} else {
dev_dbg(dev, "NRDY line has no IRQ -- tick-poll only\n");
}
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:234
- This block uses '///' (C++-style) comments. The kernel generally expects /* / (or kernel-doc /* */ when appropriate) and checkpatch will flag '//' comments outside of the SPDX line.
This issue also appears in the following locations of the same file:
- line 331
- line 343
/// @brief Submit the oldest queued TX slot (if any) via spiTransportSend().
/// A success return proves the *previous* in-flight slot (if any) is
/// now done -- the core only accepts a new send once the last one's
/// final chunk is confirmed -- so that previous slot is freed right
/// here, not after a guessed timeout. Called once per tick thread
arch/arm64/boot/dts/freescale/imx8mm-evk-spi-transport.dts:37
- This DTS introduces a new compatible string ("multitracks,spi-transport") and new vendor properties (mt-nss-gpios/mt-nrdy-gpios) but doesn’t add a devicetree binding document under Documentation/devicetree/bindings/. Without it, dtbs_check can’t validate the schema for these properties.
mt_transport0: spi@0 {
compatible = "multitracks,spi-transport";
reg = <0>;
spi-max-frequency = <500000>; /* matches mt-connect.dts; revisit once link timing is measured */
mt-nss-gpios = <&gpio5 13 GPIO_ACTIVE_HIGH>;
mt-nrdy-gpios = <&gpio4 29 GPIO_ACTIVE_HIGH>;
};
- Converted three C++-style '///' Doxygen comment blocks in
spi_mt_transport_drv.c to standard kernel /* */ style -- checkpatch
flags '//' comments outside the SPDX line, and this is in-tree code
now.
- gpiod_to_irq() can return -EPROBE_DEFER if the backing IRQ chip isn't
ready yet, not just "no IRQ available" -- the old `if (nrdy_irq > 0)`
check silently and permanently disabled the IRQ optimization on that
race instead of deferring the whole probe(). Now propagates
-EPROBE_DEFER explicitly.
- Removed CONFIG_SPI_MT_TRANSPORT=m from imx_v8_defconfig itself --
defconfigs should be buildable from a plain checkout, and this one
can't be (core/ only exists via Yocto staging). Moved to a proper
Yocto config fragment instead (imx8mmini-bb-evk's
meta-mt-transport-evk/recipes-kernel/linux-imx/files/
spi-mt-transport.cfg, wired via SRC_URI).
- Added a devicetree binding doc
(Documentation/devicetree/bindings/spi/multitracks,spi-transport.yaml)
for the multitracks,spi-transport compatible string and its
mt-nss-gpios/mt-nrdy-gpios properties, so dtbs_check can validate the
schema.
- Fixed two more stale references in the .dts file's own comment
("vendored core", gpiod_get/set_value() without _cansleep).
Found by Copilot's second review pass on PR MultiTracksDotCom#46.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Documentation/devicetree/bindings/spi/multitracks,spi-transport.yaml:55
- With
allOfschemas in place,additionalProperties: falsetends to be too strict because it can reject properties coming from the referenced schemas;unevaluatedProperties: falseis the more typical pattern for DT schema composition in this tree (e.g.Documentation/devicetree/bindings/spi/spi-mux.yaml:52). Also,spi-max-frequencyis generally expected to be specified for SPI peripherals; requiring it improves validation.
required:
- compatible
- reg
- mt-nss-gpios
- mt-nrdy-gpios
additionalProperties: false
drivers/spi/spi-mt-transport/Makefile:16
- This module’s Makefile hard-requires a non-versioned, gitignored
core/directory that is absent from the tree, so enablingCONFIG_SPI_MT_TRANSPORToutside the Yocto staging flow fails with an opaque "No rule to make target" error. Even if the Yocto path is the intended one, it’s useful to fail fast with a clear, actionable error message when the core hasn’t been staged.
# hook *before* this Makefile ever runs. Enabling CONFIG_SPI_MT_TRANSPORT
# outside that pipeline will fail with a generic "No rule to make target
# core/spi_transport.o" -- that's expected, not a bug; get the core staged
# first (or just build via bitbake, which always does this automatically).
obj-$(CONFIG_SPI_MT_TRANSPORT) += spi-mt-transport.o
Documentation/devicetree/bindings/spi/multitracks,spi-transport.yaml:25
- Building this schema with
additionalProperties: falsebut without referencing the common SPI peripheral properties prevents use of standard SPI DT flags (e.g.spi-cpha,spi-cpol, etc.) and diverges from common SPI binding patterns (seeDocumentation/devicetree/bindings/spi/spi-mux.yaml:32). Consider adding a$reftospi-peripheral-props.yamlso the binding can validate/allow standard SPI properties consistently.
This issue also appears on line 49 of the same file.
properties:
- multitracks,spi-transport.yaml: additionalProperties: false without referencing spi-peripheral-props.yaml prevented standard SPI properties (spi-cpha, spi-cpol, etc.) and diverged from this tree's own SPI binding convention (see spi-mux.yaml). Switched to allOf + $ref: spi-peripheral-props.yaml# + unevaluatedProperties: false, and added spi-max-frequency to required (generally expected for SPI peripherals). - Makefile: added an explicit $(error ...) check for core/'s presence before the obj-y list, so a build attempted outside the Yocto pipeline fails immediately with a clear message instead of an opaque "No rule to make target core/spi_transport.o" deep in the build. Found by Copilot's third review pass on PR MultiTracksDotCom#46.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:556
- gpiod_to_irq() can legally return 0 as a valid IRQ number; checking
> 0will incorrectly treat IRQ0 as “no IRQ” and skip the threaded handler. Kernel drivers generally treat only negative values as errors (e.g. drivers/pps/clients/pps-gpio.c:175-180).
if (priv->nrdy_irq == -EPROBE_DEFER)
return dev_err_probe(dev, -EPROBE_DEFER, "NRDY IRQ not ready yet\n");
if (priv->nrdy_irq > 0) {
ret = devm_request_threaded_irq(dev, priv->nrdy_irq, NULL,
- gpiod_to_irq() can legally return 0 as a valid IRQ number on some platforms/irqdomains; the probe()'s "if (nrdy_irq > 0)" check treated IRQ0 as "no IRQ available" and silently fell back to tick-poll only. Changed to ">= 0" per gpiod_to_irq()'s own contract (only negative values mean "no IRQ"), matching the convention in drivers/pps/clients/pps-gpio.c. - mt_transport_remove() had a teardown race: the NRDY threaded IRQ is devm-managed, so it's only actually freed by the driver core *after* remove() returns -- it stays live through misc_deregister()/ kthread_stop()/spiTransportStop(), and a real STM32 Client peer doesn't stop toggling NRDY just because this side is unbinding. If it fired during that window, mt_transport_hw_linux_nrdy_irq() could call priv->hw.pOnReadyEvent() into a core that's mid-teardown. Added a nrdy_irq_requested flag (set only on successful IRQ registration) and an explicit devm_free_irq() as the first action in remove() -- devm_free_irq() blocks until any in-flight threaded-handler invocation finishes, so it can't race an in-flight callback either. - Makefile: the core/ fail-fast guard's $(wildcard $(src)/...) check was missing the $(srctree)/ prefix that this same file's ccflags-y line already documented as required for Yocto's out-of-tree (O=) builds -- caused a false-positive "core/ is not staged" failure even when core/ was genuinely present. Added the missing prefix.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:327
- mt_transport_misc_read() can race with multiple concurrent readers: the initial rx_valid check (or the wait_event) is not tied to the later consume-under-lock section, so two readers can both observe rx_valid=true and both copy/clear the same buffered message. Either enforce single-open semantics for the miscdevice, or re-check rx_valid under rx_lock at consume time and, if it was already consumed, retry the wait (blocking) or return -EAGAIN (non-blocking).
if (filp->f_flags & O_NONBLOCK) {
spin_lock_irqsave(&priv->rx_lock, irqflags);
if (!priv->rx_valid) {
spin_unlock_irqrestore(&priv->rx_lock, irqflags);
return -EAGAIN;
}
spin_unlock_irqrestore(&priv->rx_lock, irqflags);
} else {
ret = wait_event_interruptible(priv->rx_wq, READ_ONCE(priv->rx_valid));
if (ret)
return ret;
}
/* Snapshot into a local buffer under the lock, then copy_to_user()
* outside it -- copy_to_user() can fault/sleep, which is illegal
* while holding a spinlock. rx_valid is cleared here too (not after
* the copy) since it's rx_lock-protected state, same as rx_buf --
* a failing copy_to_user (a broken caller's bad pointer) now
* consumes the buffered message rather than leaving it for retry,
* a minor, acceptable behavior change for this placeholder interface.
*/
spin_lock_irqsave(&priv->rx_lock, irqflags);
len = priv->rx_len;
if (len > count)
len = count;
memcpy(scratch, priv->rx_buf, len);
WRITE_ONCE(priv->rx_valid, false);
spin_unlock_irqrestore(&priv->rx_lock, irqflags);
…r race) Copilot's review of commit 47c3d31 (the IRQ0/remove()-race fix) found two new issues: - priv is devm_kzalloc'd against the SPI device, so it's freed as soon as remove() returns -- but nothing stops /dev/mt_spi_transport from staying open across an unbind. The next read()/write() on a surviving fd would touch freed memory (the miscdevice itself is embedded in priv). Fixed by switching priv to a plain kzalloc + kref: one reference is held by the driver instance (taken in probe(), dropped at the end of remove()), and one more per open fd (mt_transport_misc_open()/_release()) -- priv is only actually freed once both are gone. Every probe() error path after the allocation now does kref_put() instead of relying on devm. Also added a `removed` flag, set first thing in remove(), checked in read()/write(): this is a fail-fast measure on top of the memory-safety fix above -- e.g. write()'s priv->os.pTaskNotifyGive() call would otherwise still reach into a tick thread/os_ctx that remove() just tore down, even though touching that (now up-to-date) memory itself is safe. - mt_transport_misc_read()'s rx_valid check (either the O_NONBLOCK branch or wait_event_interruptible) isn't atomic with the later lock-protected consume step, so two concurrent readers could both observe rx_valid and both consume the same buffered message. Fixed by enforcing single-open semantics (an `available` atomic_t) in mt_transport_misc_open() -- matches Copilot's own suggested resolution, and is the correct semantic for this single-channel, single-in-flight-message placeholder interface (see the file banner) with exactly one intended userspace peer, not just a workaround.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (4)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:725
- mt_transport_remove() sets priv->removed but does not wake threads blocked in read()/write() (rx_wq / tx_free_wq). This can leave existing fds stuck in wait_event_interruptible() forever after unbind. Wake both waitqueues immediately after marking removed so sleepers re-check the removed flag and exit.
WRITE_ONCE(priv->removed, true);
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:378
- Blocking read() can hang indefinitely if the SPI device is unbound while a process is sleeping in wait_event_interruptible(): the wait condition only checks rx_valid and never considers priv->removed, and mt_transport_remove() currently doesn't wake rx_wq. Include priv->removed in the wait condition and return -ENODEV if it becomes true.
This issue also appears on line 724 of the same file.
ret = wait_event_interruptible(priv->rx_wq, READ_ONCE(priv->rx_valid));
if (ret)
return ret;
}
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:475
- Blocking write() can hang indefinitely if the device is unbound while a process is sleeping in wait_event_interruptible() for TX ring space: the condition only checks for room, not priv->removed, and mt_transport_remove() doesn't wake tx_free_wq. Include priv->removed in the wait condition and fail with -ENODEV when it becomes true.
ret = wait_event_interruptible(priv->tx_free_wq, mt_transport_tx_has_room(priv));
if (ret)
return ret;
}
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:502
- poll() doesn't report device removal: after unbind, existing fds can poll forever because the waitqueue is only rx_wq and the mask never includes HUP/ERR when priv->removed is set. Return EPOLLHUP|EPOLLERR when removed so userspace can detect teardown cleanly.
poll_wait(filp, &priv->rx_wq, wait);
if (READ_ONCE(priv->rx_valid))
mask |= EPOLLIN | EPOLLRDNORM;
return mask;
Copilot's review of commit 0ddfa20 (the miscdevice UAF/reader-race fix) found the removed flag it added only helps a read()/write() call that checks it on entry -- a reader/writer already blocked in wait_event_interruptible() on rx_wq/tx_free_wq when mt_transport_remove() runs would never wake up, since neither wait condition considered removed and remove() never woke either queue. Fixed: - rx_wq's and tx_free_wq's wait_event_interruptible() conditions now also check READ_ONCE(priv->removed); the caller returns -ENODEV if that's why it woke. - mt_transport_remove() now calls wake_up_interruptible_all() on both queues immediately after setting removed, so any waiter already asleep re-checks its condition and exits instead of hanging across unbind. - mt_transport_misc_poll() now reports EPOLLHUP|EPOLLERR once removed is set, so an fd that outlives unbind can detect teardown via poll() too, not just its next read()/write().
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:56
- Kernel coding style uses
struct foo {on a single line; the current formatting forstruct mt_transport_tx_slotis likely to trigger checkpatch/style warnings and is inconsistent with the rest of this file (e.g.struct mt_transport_priv {).
struct mt_transport_tx_slot
{
uint8_t buf[SPI_TRANSPORT_CHANNEL_MESSAGE_MAX];
uint16_t len;
};
struct mt_transport_tx_slot had its opening brace on its own line, inconsistent with Linux kernel style and every other struct in this file (e.g. struct mt_transport_priv). Matches checkpatch's expectation.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:289
- tx_in_flight_idx is set on every successful spiTransportSend(), but it is never cleared back to -1 anywhere. As a result, mt_transport_tx_room_locked() will permanently treat one slot as occupied even after the final in-flight TX finishes, reducing the usable TX queue depth by 1 for the lifetime of the device and potentially causing unexpected -EAGAIN/-blocking earlier than MT_TRANSPORT_TX_QUEUE_DEPTH implies. Consider clearing tx_in_flight_idx when the core indicates the in-flight send has fully completed (and no queued slots remain), and waking tx_free_wq at that point.
spin_lock_irqsave(&priv->tx_lock, flags);
priv->tx_queued_count--;
priv->tx_in_flight_idx = idx;
priv->tx_head = (priv->tx_head + 1) % MT_TRANSPORT_TX_QUEUE_DEPTH;
spin_unlock_irqrestore(&priv->tx_lock, flags);
Copilot's review correctly noted tx_in_flight_idx is set on every successful spiTransportSend() but never cleared back to -1, so mt_transport_tx_room_locked() permanently counts one slot as occupied from the first successful send onward -- DEPTH-1 usable slots instead of DEPTH. Not fixing inline: this is a symptom of an already-tracked core API gap (MT-158925 item 1 -- spiTransportSend() has no TX-completion signal), not something the driver can correctly resolve on its own. A driver-side guess (e.g. a timeout) would reintroduce the exact flat-timeout anti-pattern MT-158925 already documents as having cost ~200x real throughput earlier in this ticket. Documented in code with the tracking reference instead, matching this file's existing convention for deliberately-deferred decisions (see the hardcoded misc device name comment).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:723
- If spiTransportStart() fails after the NRDY IRQ was requested, priv is freed via kref_put() while the devm-managed IRQ can still fire until devres unwind after probe returns. Free the IRQ on this error path too to avoid a potential UAF in the threaded handler.
if (spiTransportStart(priv->htransport) != eSpiTransportErrorNone) {
kthread_stop(priv->tick_thread);
kref_put(&priv->refcount, mt_transport_priv_release);
return dev_err_probe(dev, -EINVAL, "spiTransportStart failed\n");
}
drivers/spi/spi-mt-transport/spi_mt_transport_drv.c:744
- If misc_register() fails after the NRDY IRQ was requested, priv is freed via kref_put() while the devm-managed IRQ can still fire until devres unwind after probe returns. Free the IRQ in this failure path (ideally before stopping the core) to avoid the threaded handler calling into torn-down state or freed hw_ctx.
ret = misc_register(&priv->misc);
if (ret) {
/* kthread_stop() before spiTransportStop(): the tick thread
* must not be able to call into the core after it's been
* stopped (same ordering as mt_transport_remove() below).
*/
kthread_stop(priv->tick_thread);
spiTransportStop(priv->htransport);
kref_put(&priv->refcount, mt_transport_priv_release);
return dev_err_probe(dev, ret, "misc_register failed\n");
drivers/spi/Kconfig:1217
- SPI_MT_TRANSPORT depends on COMPILE_TEST, but this module intentionally does not build from a plain tree checkout unless an external core/ directory is staged (and the Makefile hard-errors when it is not). With COMPILE_TEST=y, common config targets like allmodconfig/allyesconfig can select this symbol and break builds. Consider dropping COMPILE_TEST here (or making the driver self-contained in-tree).
config SPI_MT_TRANSPORT
tristate "MultiTracks SPI transport protocol driver (Host role)"
depends on ARCH_MXC || COMPILE_TEST
depends on GPIOLIB
…AF, COMPILE_TEST) - Converting priv to a kref-refcounted allocation (previous round) fixed the miscdevice-outliving-unbind UAF, but introduced a narrower one: probe()'s error paths after the NRDY IRQ is successfully requested (tick_thread failure, spiTransportStart failure, misc_register failure) now call kref_put(), which can free priv's memory (including the embedded hw_ctx the IRQ handler dereferences) synchronously and immediately -- unlike the old devm_kzalloc, which deferred that same free to the same devm unwind pass that frees the IRQ first (reverse registration order), so the two frees used to always happen in a safe order automatically. Fixed by explicitly freeing the IRQ (mirroring mt_transport_remove()'s own guard) before kref_put() in all three paths -- Copilot's review only flagged two of these (spiTransportStart, misc_register); the tick_thread failure path has the identical bug since IRQ registration happens even earlier in probe(), fixed here too. - SPI_MT_TRANSPORT depended on "ARCH_MXC || COMPILE_TEST", but this module's core/ only exists once Yocto's meta-mt-transport-evk stages it in -- COMPILE_TEST=y would let allmodconfig/allyesconfig CI builds select it outside that pipeline and hit the Makefile's hard error. Dropped COMPILE_TEST from the depends line.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
drivers/spi/spi-mt-transport/spi_transport_os_linux.c:105
- mt_os_log() takes a va_list parameter, but initializes struct va_format with
.va = &args. On architectures whereva_listis an array type, function parameters of array type are adjusted to pointers, so&argsbecomes a pointer-to-pointer rather than ava_list *, which can break%pVformatting. Make a localva_listcopy and pointva_format.vaat that local.
struct mt_transport_os_ctx *ctx = pContext;
struct va_format vaf = { .fmt = pFormat, .va = &args };
dev_dbg(ctx->dev, "%pV", &vaf);
}
mt_os_log() built struct va_format with `.va = &args`, where args is a va_list function parameter. On architectures where va_list is an array type, array-type parameters decay to pointers, so &args points at the local decayed pointer rather than a real va_list object -- %pV's va_arg()-based consumption of vaf->va would then be operating on the wrong representation. Fixed with the standard portable idiom: va_copy() into a genuinely local va_list and point .va at that copy instead, which is valid regardless of the platform's va_list representation.
MT-158113
Summary
Scope grew 2026-08-19: this PR originally added just the devicetree overlay, with the driver itself living out-of-tree in the firmware repo. The driver's Linux adapter (
drivers/spi/spi-mt-transport/) is now back in-tree here instead -- it's genuinely GPL (calls GPL-only-exported kernel symbols core to its function:devm_gpiod_get,gpiod_get_value/gpiod_set_value/gpiod_to_irq,spi_async,spi_slave_abort,dev_err_probe,sysfs_emit-- confirmed against the actual built kernel source, not assumed), and the firmware repo'sCODING_STANDARDS.md§7 forbids GPL/LGPL code there. Found by Copilot's review on firmware PR #785; relicensing wasn't an option sinceMODULE_LICENSE("GPL")is required for these symbols to resolve at all, not a style choice.What's in this PR now:
arch/arm64/boot/dts/freescale/imx8mm-evk-spi-transport.dts(+Makefileentry) -- the devicetree overlay:multitracks,spi-transportbinding on ECSPI2 (SCLK/MOSI/MISO/SS0) plusmt-nss-gpios/mt-nrdy-gpiosproperties for the protocol's NSS/NRDY handshake lines. Unchanged from this PR's original scope.drivers/spi/spi-mt-transport/-- the Linux Host-role driver adapter (spi_mt_transport_drv.cglue,spi_transport_hw_linux.*/spi_transport_os_linux.*HW/OS adapters,kernel-compat/*shims for the portable core's hosted-C11 headers under the kernel's-nostdincbuild), moved here verbatim from the firmware repo, content unchanged. Plus the matchingdrivers/spi/Kconfig(CONFIG_SPI_MT_TRANSPORT) anddrivers/spi/Makefilehooks, andarch/arm64/configs/imx_v8_defconfig's=mline.What's deliberately NOT in this PR: the portable protocol core (
spi_transport.c/spi_transport_channel.c/spi_transport_frame.c/spi_transport_crc16.c/spi_transport_hw.c+ their headers). That stays the firmware repo's single source of truth -- it's plain C11, calls no kernel APIs, and doesn't need to be GPL.imx8mmini-bb-evk'smeta-mt-transport-evk/recipes-kernel/linux-imx/linux-imx_%.bbappendfetches it fresh from the firmware repo at Yocto build time (a second, separately-namedSRC_URIentry) and stages it intodrivers/spi/spi-mt-transport/core/via ado_patch[postfuncs]hook -- gitignored here, never committed, so there's no duplication between the two repos. This mirrors the original pre-restructuring layout's directory shape exactly, just populated at build time instead of vendored into git history.Test plan
bitbake -c patch linux-imx -f-- confirmed the core-staging postfunc correctly populatesdrivers/spi/spi-mt-transport/core/(5.cfiles, 6 headers) in the shared kernel source tree.bitbake linux-imx-- in-tree driver compiles clean, packages automatically askernel-module-spi-mt-transport-<kernel-ver>(standard in-tree convention -- no manualIMAGE_INSTALLentry needed, unlike the old out-of-tree recipe).bitbake imx-image-core-- fresh manifest confirmed the module package present.uname -rconfirms the new kernel,modprobe spi_mt_transportprobes cleanly,link_state: connected,event_countersclean.mismatchcounter incrementing exactly once per write.🤖 Generated with Claude Code