* [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers
@ 2026-08-27 7:54 Ayush Singh
2026-08-27 7:54 ` [PATCH v2 1/7] greybus: connection: Export gb_connection_get() and gb_connection_put() Ayush Singh
` (6 more replies)
0 siblings, 7 replies; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
A Greybus network needs an SVC (Supervisory Controller) to bring
interfaces up, assign device IDs and connect CPorts to the AP. On a
UniPro network the SVC is a real entity on the bus. On transports that
merely carry Greybus messages - a UART, an I2C bus, a network link -
nothing on the wire plays that role, so the SVC has to be emulated
somewhere: in a user-space bridge (gbridge), in coprocessor firmware
(cc1352p7 in gb-beagleplay), or open-coded inside the host driver itself.
This series moves the emulation into the kernel and makes it shared.
This was discussed in a prior RFC as well [2], but to keep things short,
removing the need for an external SVC greatly simplifies the greybus
network setup when nodes are directly connected over common transports
such as UART, I2C etc.
gb-uart-node gb-softsvc greybus core
(transport, serdev) <-> (SVC + host device) <-> (bundles, protocols)
NodeOps module_insert()
submit_message() module_remove()
gb-softsvc registers itself as a Greybus host device and answers the
SVC-side operations the core expects during interface bring-up and
teardown. A host driver implements the NodeOps trait to push data
towards its node, calls module_insert() to announce a new node,
submit_message() to hand incoming Greybus messages back to the core,
and module_remove() on disconnect.
The first user is gb-uart-node, a driver for Greybus nodes attached over
a plain serial port. Framing is HDLC, with a one-byte address (0x01 for
Greybus) and control byte, followed by the 16-bit CPort ID and the
Greybus message. No SVC firmware is required on the far end, so the node
can be a bare microcontroller speaking Greybus - a BeagleConnect Freedom
over its serial link, in this case. The testing is performed with
greybus-zephyr [0] implementation.
Both drivers are written in Rust, which is why the middle of the series
is abstractions rather than drivers. Only the APIs these two drivers
need are covered: protocols.rs abstracts the SVC-facing parts of
greybus_protocols.h, and types such as Greybus Interface do not
implement AlwaysRefCounted yet. The intent is to grow this as more Rust
host drivers appear rather than to abstract the whole subsystem up
front.
Patches 1 and 2 are small C-side preparations to the Greybus core:
exporting gb_connection_get()/gb_connection_put() and adding
gb_connection_hd_find_by_intf(), a lookup by remote interface and CPort
id for callers that only know the far end of a connection. Patch 3 adds
a CRC-CCITT abstraction, needed for HDLC frame checks. Patch 4 adds the
Greybus abstractions, patches 5 and 6 the two drivers, and patch 7 the
device tree binding for BeagleConnect Freedom.
Open questions
***************
- gb-uart-node imports types from gb-softsvc, so the series carries the
Rust-to-Rust cross-module calling setup from nova-core [1]:
gb_softsvc_exports.c plus the Makefile plumbing that emits crate
metadata and generates the export list. This is a workaround for the
build system not supporting Rust cross-module dependencies natively,
and it should go away once that lands.
- Connection create/destroy and interface activate/resume in gb-softsvc
currently just acknowledge the request. Callbacks into NodeOps can be
added when a transport actually needs to act on them; I did not want
to invent an interface without a user.
- Zerocopy is currently not being used in grebeybus/protocols.rs. They
cannot be derived yet since types generated by bindgen do not have
them, and it seems explicitly forbindden to manually impl the traits.
So using old traits from transmute.
- The bindings are supposed to be created for actual device, but any MCU
that supports Zephyr, can run the greybus-zephyr firmware with UART
transport. So not sure if adding a beagle,beagleconnect-freedom
compatible is the correct choice here.
- The individual patches can be spun off into independent patch series
if required. The reason for this single patch series is to provide a
complete picture of usage.
- Since gb-softsvc currently is not being used from a C driver, no C API
is provided. However, if required, it can be added.
- Writing to UART from gb-uart-node is currently a bit broken. I am not
quite sure what the safe way is to go from a non-bound device to a
bound device. Any suggestions on this front are welcome.
- I am not sure if Rust abstractions should have a seperate entry in
MAINTAINERS with me as the maintainer, or if they should just be added
to the respective subsystem entries.
[0]: https://github.com/beagleboard/greybus-zephyr
[1]: https://lore.kernel.org/all/20260622-nova-exports-v5-0-6191773fc977@nvidia.com/
[2]: https://lore.kernel.org/all/ecca8eb2-8e5a-4770-bcf6-3fb49773088b@beagleboard.org/
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
Changes in v2:
- Fix possible null pointer dereference in
gb_connection_hd_find_by_intf. Flagged by sashiko-bot.
- Remove prompt CONFIG_RUST_CRC_CCITT_ABSTRACTIONS Kconfig symbol.
- Add CONFIG_RUST_GREYBUS_ABSTRACTIONS to ensure that greybus is
built-in for rust abstractions to work.
- Use gfp_mask in message_send callback.
- Only provide a Message reference in message_cancel callback.
- Add invariant comment for Registration.
- Call Registration->add directly in new, before constructing
Registration.
- Add Send and Sync bounds to T in Registration<T>
- Set endo_id as 0. Is not used anywhere.
- Fix module_insert intf_count. Was hardcoded to 1 by mistake. Flagged by
sashiko-bot.
- Check for empty intfs slice in Module::new.
- Remove module in serdev::Driver::unbind instead of on drop.
- Make gb_uart_node write atomic. Using temp buffer to build frame.
- Fix import style.
- Reorder beagleconnect-freedom dtbinding patch to be before gb_uart_node.
- Add vbat-supply and reg properties to beagleconnect-freedom dtbinding.
- Reference spi-peripheral-props in beagleconnect-freedom dtbinding.
- Link to v1: https://lore.kernel.org/r/20260820-gb-uart-transport-v1-0-282da14ab7b7@beagleboard.org
---
Ayush Singh (7):
greybus: connection: Export gb_connection_get() and gb_connection_put()
greybus: connection: Add gb_connection_hd_find_by_intf()
rust: crc_ccitt: add CRC-CCITT abstraction
rust: kernel: Add greybus abstractions
drivers: greybus: Add software SVC implementation
dt-bindings: beagle: Add BeagleConnect Freedom
greybus: Add Rust UART node driver
.../beagle/beagle,beagleconnect-freedom.yaml | 39 ++
MAINTAINERS | 15 +
drivers/greybus/.gitignore | 1 +
drivers/greybus/Kconfig | 34 ++
drivers/greybus/Makefile | 50 ++
drivers/greybus/connection.c | 29 +-
drivers/greybus/gb_softsvc.rs | 504 +++++++++++++++++++++
drivers/greybus/gb_softsvc_exports.c | 15 +
drivers/greybus/gb_uart_node.rs | 245 ++++++++++
include/linux/greybus/connection.h | 6 +
lib/crc/Kconfig | 7 +
rust/bindings/bindings_helper.h | 2 +
rust/kernel/alloc.rs | 5 +
rust/kernel/crc_ccitt.rs | 26 ++
rust/kernel/greybus/hd.rs | 333 ++++++++++++++
rust/kernel/greybus/mod.rs | 232 ++++++++++
rust/kernel/greybus/protocols.rs | 392 ++++++++++++++++
rust/kernel/lib.rs | 4 +
18 files changed, 1937 insertions(+), 2 deletions(-)
---
base-commit: 6b8c8af514d739d0335f5579b585e02babe8a727
change-id: 20260810-gb-uart-transport-9255d6557c4c
Best regards,
--
Ayush Singh <ayush@beagleboard.org>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 1/7] greybus: connection: Export gb_connection_get() and gb_connection_put()
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 7:54 ` [PATCH v2 2/7] greybus: connection: Add gb_connection_hd_find_by_intf() Ayush Singh
` (5 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
gb_connection_get() and gb_connection_put() are currently private to
the core. Drivers that hold a connection reference obtained from a
lookup helper need to be able to drop it, and drivers that cache a
connection pointer need to be able to take one.
Make both functions non-static, declare them in the greybus connection
header, and export them so modular drivers can manage connection
references. The first user is Rust greybus abstractions, added later in
this series.
No functional change.
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
drivers/greybus/connection.c | 6 ++++--
include/linux/greybus/connection.h | 3 +++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/greybus/connection.c b/drivers/greybus/connection.c
index bd04485decb3..f8fda22bc466 100644
--- a/drivers/greybus/connection.c
+++ b/drivers/greybus/connection.c
@@ -33,19 +33,21 @@ static bool gb_connection_cport_in_use(struct gb_interface *intf, u16 cport_id)
return false;
}
-static void gb_connection_get(struct gb_connection *connection)
+void gb_connection_get(struct gb_connection *connection)
{
kref_get(&connection->kref);
trace_gb_connection_get(connection);
}
+EXPORT_SYMBOL_GPL(gb_connection_get);
-static void gb_connection_put(struct gb_connection *connection)
+void gb_connection_put(struct gb_connection *connection)
{
trace_gb_connection_put(connection);
kref_put(&connection->kref, gb_connection_kref_release);
}
+EXPORT_SYMBOL_GPL(gb_connection_put);
/*
* Returns a reference-counted pointer to the connection if found.
diff --git a/include/linux/greybus/connection.h b/include/linux/greybus/connection.h
index d59b7fc1de3e..b53aad2270d6 100644
--- a/include/linux/greybus/connection.h
+++ b/include/linux/greybus/connection.h
@@ -128,4 +128,7 @@ static inline void gb_connection_set_data(struct gb_connection *connection,
connection->private = data;
}
+void gb_connection_get(struct gb_connection *connection);
+void gb_connection_put(struct gb_connection *connection);
+
#endif /* __CONNECTION_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 2/7] greybus: connection: Add gb_connection_hd_find_by_intf()
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
2026-08-27 7:54 ` [PATCH v2 1/7] greybus: connection: Export gb_connection_get() and gb_connection_put() Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 8:13 ` sashiko-bot
2026-08-27 7:54 ` [PATCH v2 3/7] rust: crc_ccitt: add CRC-CCITT abstraction Ayush Singh
` (4 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
gb_connection_hd_find() looks up a connection on a host device by its
host-side CPort id. Callers that only know the remote end of a
connection (the interface id and the CPort id) have no way to find it.
Add gb_connection_hd_find_by_intf(), which walks the host device's
connection list and returns a reference-counted pointer to the matching
connection, or NULL if there is none. The caller is responsible for
dropping the reference with gb_connection_put(). The first user is
gb-softsvc driver, added later in this series.
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
drivers/greybus/connection.c | 23 +++++++++++++++++++++++
include/linux/greybus/connection.h | 3 +++
2 files changed, 26 insertions(+)
diff --git a/drivers/greybus/connection.c b/drivers/greybus/connection.c
index f8fda22bc466..dc9eb49a69bc 100644
--- a/drivers/greybus/connection.c
+++ b/drivers/greybus/connection.c
@@ -71,6 +71,29 @@ gb_connection_hd_find(struct gb_host_device *hd, u16 cport_id)
return connection;
}
+struct gb_connection *gb_connection_hd_find_by_intf(struct gb_host_device *hd,
+ u8 intf_id, u16 intf_cport)
+{
+ struct gb_connection *connection;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gb_connections_lock, flags);
+ list_for_each_entry(connection, &hd->connections, hd_links) {
+ if (connection->intf &&
+ connection->intf->interface_id == intf_id &&
+ connection->intf_cport_id == intf_cport) {
+ gb_connection_get(connection);
+ goto found;
+ }
+ }
+ connection = NULL;
+found:
+ spin_unlock_irqrestore(&gb_connections_lock, flags);
+
+ return connection;
+}
+EXPORT_SYMBOL_GPL(gb_connection_hd_find_by_intf);
+
/*
* Callback from the host driver to let us know that data has been
* received on the bundle.
diff --git a/include/linux/greybus/connection.h b/include/linux/greybus/connection.h
index b53aad2270d6..589721d46015 100644
--- a/include/linux/greybus/connection.h
+++ b/include/linux/greybus/connection.h
@@ -131,4 +131,7 @@ static inline void gb_connection_set_data(struct gb_connection *connection,
void gb_connection_get(struct gb_connection *connection);
void gb_connection_put(struct gb_connection *connection);
+struct gb_connection *gb_connection_hd_find_by_intf(struct gb_host_device *hd,
+ u8 intf_id, u16 intf_cport);
+
#endif /* __CONNECTION_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 3/7] rust: crc_ccitt: add CRC-CCITT abstraction
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
2026-08-27 7:54 ` [PATCH v2 1/7] greybus: connection: Export gb_connection_get() and gb_connection_put() Ayush Singh
2026-08-27 7:54 ` [PATCH v2 2/7] greybus: connection: Add gb_connection_hd_find_by_intf() Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 7:54 ` [PATCH v2 4/7] rust: kernel: Add greybus abstractions Ayush Singh
` (3 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
The module is gated behind the new RUST_CRC_CCITT_ABSTRACTIONS Kconfig
symbol, which selects CRC_CCITT, so the C library is only built when a
Rust user actually needs it.
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
MAINTAINERS | 1 +
lib/crc/Kconfig | 7 +++++++
rust/bindings/bindings_helper.h | 1 +
rust/kernel/crc_ccitt.rs | 26 ++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 ++
5 files changed, 37 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6008f16ae2ca..1e3b42eff741 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6921,6 +6921,7 @@ T: git https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git crc-ne
F: Documentation/staging/crc*
F: include/linux/crc*
F: lib/crc/
+F: rust/kernel/crc*
F: scripts/gen-crc-consts.py
CREATIVE SB0540
diff --git a/lib/crc/Kconfig b/lib/crc/Kconfig
index 927fc6a6b2b9..628da2d30fc8 100644
--- a/lib/crc/Kconfig
+++ b/lib/crc/Kconfig
@@ -32,6 +32,13 @@ config CRC_CCITT
The CRC-CCITT library functions. Select this if your module uses any
of the functions from <linux/crc-ccitt.h>.
+config RUST_CRC_CCITT_ABSTRACTIONS
+ bool
+ depends on RUST
+ select CRC_CCITT
+ help
+ This enables the Rust abstraction for the CRC-CCITT API.
+
config CRC_ITU_T
tristate
help
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 24d659a87c1d..6eb3caaee497 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -49,6 +49,7 @@
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/cpumask.h>
+#include <linux/crc-ccitt.h>
#include <linux/cred.h>
#include <linux/debugfs.h>
#include <linux/device/faux.h>
diff --git a/rust/kernel/crc_ccitt.rs b/rust/kernel/crc_ccitt.rs
new file mode 100644
index 000000000000..6042ada16967
--- /dev/null
+++ b/rust/kernel/crc_ccitt.rs
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! CRC-CCITT computation.
+//!
+//! C header: [`include/linux/crc-ccitt.h`](srctree/include/linux/crc-ccitt.h)
+
+/// Computes the CRC-CCITT of `data`, starting from the seed value `crc`.
+///
+/// Pass the result back in as `crc` to compute the checksum of a buffer
+/// incrementally.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::crc_ccitt::crc_ccitt;
+///
+/// let one_shot = crc_ccitt(0xffff, b"hello world");
+/// let split = crc_ccitt(crc_ccitt(0xffff, b"hello "), b"world");
+/// assert_eq!(one_shot, split);
+/// ```
+#[inline]
+pub fn crc_ccitt(crc: u16, data: &[u8]) -> u16 {
+ // SAFETY: `data.as_ptr()` is valid for reads of `data.len()` bytes for the
+ // duration of the call, since it is derived from a live shared slice.
+ unsafe { bindings::crc_ccitt(crc, data.as_ptr(), data.len()) }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 3edc343caba5..45c4f4db51d2 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -61,6 +61,8 @@
#[cfg(CONFIG_CPU_FREQ)]
pub mod cpufreq;
pub mod cpumask;
+#[cfg(CONFIG_RUST_CRC_CCITT_ABSTRACTIONS)]
+pub mod crc_ccitt;
pub mod cred;
pub mod debugfs;
pub mod device;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 4/7] rust: kernel: Add greybus abstractions
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
` (2 preceding siblings ...)
2026-08-27 7:54 ` [PATCH v2 3/7] rust: crc_ccitt: add CRC-CCITT abstraction Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 8:12 ` sashiko-bot
2026-08-27 7:54 ` [PATCH v2 5/7] drivers: greybus: Add software SVC implementation Ayush Singh
` (2 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
Add Rust abstractions for the Greybus core, enough to implement a
Greybus host driver in Rust.
The abstractions are split into 3 modules:
- Top level mod.rs: Basic abstractions greybus structures.
- hd.rs: Greybus Host Device specific abstractions.
- protocols.rs: Abstractions for greybus_protocols.h, i.e. greybus
protocol types and constants.
Only the APIs used by gb-softsvc and gb-uart-node driver are covered.
As such, protocols.rs also only contains abstractions for greybus
structures that are used by SVC and some types such as Greybus Interface
do not implement AlwaysRefCounted at the moment.
HdDriver Note
**************
In C message_send callback, it is possible for the caller to defer
calling gb_message_sent. This is used in es2 driver. However, looking at
the internals, it does not seem like the message owenership is actually
transferred to the caller in real terms. Instead, it is more like the
caller is shared ownership. This is evident ny the fact that
message_cancel can be called at any point, and depending on wheather the
message is request or response, the message can become invalid after the
message_cancel callback. Thus the driver is expected to ensure that no
dangling references to that message exist.
Since gb_message is not an internally ref counted structure in true
terms (it does seem to use the ref counting in the internal operation),
and since double calls to gb_message_sent are not really allowed, I have
made the decision to call gb_message_sent on a message in the
abstractions rather than making the driver call it. This means the
driver only gets a reference to the message, and never owns it. This
does prevent a driver to use it in the way es2 currently does, but seems
much less of a footgun.
Another possibility would be to implement AlwaysRefCounted on Message
and pass the calls to gb_operation_get/put, but I have not gone through
the internals enough to figure out if this is correct in all cases.
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
MAINTAINERS | 1 +
drivers/greybus/Kconfig | 7 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/alloc.rs | 5 +
rust/kernel/greybus/hd.rs | 333 +++++++++++++++++++++++++++++++++
rust/kernel/greybus/mod.rs | 232 +++++++++++++++++++++++
rust/kernel/greybus/protocols.rs | 392 +++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
8 files changed, 973 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 1e3b42eff741..80247a031353 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11338,6 +11338,7 @@ F: drivers/greybus/
F: drivers/staging/greybus/
F: include/linux/greybus.h
F: include/linux/greybus/
+F: rust/kernel/greybus/
GREYBUS UART PROTOCOLS DRIVERS
M: David Lin <dtwlin@gmail.com>
diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
index c3f056d28b01..3bf3b8691442 100644
--- a/drivers/greybus/Kconfig
+++ b/drivers/greybus/Kconfig
@@ -15,6 +15,13 @@ menuconfig GREYBUS
To compile this code as a module, choose M here: the module
will be called greybus.ko
+config RUST_GREYBUS_ABSTRACTIONS
+ bool "Rust greybus abstractions"
+ depends on RUST
+ select GREYBUS
+ help
+ This enables the Rust abstraction for the Greybus API.
+
if GREYBUS
config GREYBUS_BEAGLEPLAY
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 6eb3caaee497..ded5300e75da 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -62,6 +62,7 @@
#include <linux/file.h>
#include <linux/firmware.h>
#include <linux/fs.h>
+#include <linux/greybus.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
diff --git a/rust/kernel/alloc.rs b/rust/kernel/alloc.rs
index 21067bde6860..d0ba4d91b3d3 100644
--- a/rust/kernel/alloc.rs
+++ b/rust/kernel/alloc.rs
@@ -38,6 +38,11 @@
pub struct Flags(u32);
impl Flags {
+ /// Create from gfp_t
+ pub(crate) fn new(v: bindings::gfp_t) -> Self {
+ Self(v)
+ }
+
/// Get the raw representation of this flag.
pub(crate) fn as_raw(self) -> u32 {
self.0
diff --git a/rust/kernel/greybus/hd.rs b/rust/kernel/greybus/hd.rs
new file mode 100644
index 000000000000..ee2a4930ff2e
--- /dev/null
+++ b/rust/kernel/greybus/hd.rs
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Greybus host device abstractions.
+//!
+//! A driver implements the transmit path through [`HdDriver`] and feeds received data back into
+//! the core with [`Device::data_rcvd`].
+//!
+//! A host device is created, populated with driver private data and added to the Greybus bus by
+//! constructing a [`Registration`], which owns the underlying `struct gb_host_device`. Dropping
+//! the registration drops the private data and removes the device from the bus, so it is normally
+//! stored in the driver data of the parent device and torn down implicitly when that device goes
+//! away.
+//!
+//! Individual references to a live host device are represented by [`ARef<Device>`], which keeps
+//! the embedded `struct device` reference count balanced.
+//!
+//! C header: [`include/linux/greybus/hd.h`](srctree/include/linux/greybus/hd.h)
+//!
+//! # Examples
+//!
+//! ```ignore
+//! use kernel::{device, prelude::*};
+//!
+//! struct MyHd {
+//! // transport state
+//! }
+//!
+//! #[vtable]
+//! impl HdDriver for MyHd {
+//! fn message_send(data: &Self, dest_cport_id: u16, msg: Message) -> Result {
+//! // Copy what is needed out of `msg`, queue it, and return without sleeping.
+//! // Call `HostDevice::message_sent()` once the core may release the message.
+//! Ok(())
+//! }
+//!
+//! fn message_cancel(_msg: Message) {}
+//! }
+//!
+//! fn probe(parent: &device::Device) -> Result<Registration<MyHd>> {
+//! Registration::new(parent, BUFFER_SIZE_MAX, NUM_CPORTS, try_pin_init!(MyHd {}))
+//! }
+//! ```
+
+use core::{
+ marker::PhantomData,
+ ptr::{
+ addr_of_mut,
+ NonNull, //
+ },
+};
+
+use crate::{
+ alloc::Flags,
+ device,
+ error::{
+ code,
+ from_err_ptr,
+ to_result, //
+ },
+ greybus::{
+ Connection,
+ Message, //
+ },
+ prelude::*,
+ sync::aref::{
+ ARef,
+ AlwaysRefCounted, //
+ },
+ types::Opaque,
+};
+
+/// The set of callbacks a Greybus host driver can provide.
+#[vtable]
+pub trait HdDriver: Send + Sync + Sized + 'static {
+ /// Transmits `msg` to `dest_cport_id`.
+ ///
+ /// This may be called in atomic context and therefore must not sleep; queue the message and
+ /// return. Once the core is allowed to release the message, call
+ /// [`HostDevice::message_sent`].
+ ///
+ /// The message is only borrowed for the duration of this call: copy what is needed out of it,
+ /// do not stash the reference.
+ fn message_send(data: &Self, dest_cport_id: u16, msg: &Message, gfp_mask: Flags) -> Result;
+
+ /// Aborts the transmission of a message previously handed to [`HdDriver::message_send`].
+ ///
+ /// Always called in process context.
+ fn message_cancel(msg: &Message);
+}
+
+/// Builds the C callback table for a [`HdDriver`] implementation.
+struct HdDriverVTable<T: HdDriver>(PhantomData<T>);
+
+impl<T: HdDriver> HdDriverVTable<T> {
+ const DRIVER: bindings::gb_hd_driver = bindings::gb_hd_driver {
+ // Both are mandatory, hence no `HAS_*` check. See [`HdDriver`].
+ message_send: Some(Self::message_send),
+ message_cancel: Some(Self::message_cancel),
+ // Every other callback is left `NULL`, which the Greybus core takes as "use the default
+ // behaviour".
+ ..pin_init::zeroed()
+ };
+
+ const fn build() -> &'static bindings::gb_hd_driver {
+ &Self::DRIVER
+ }
+
+ /// # Safety
+ ///
+ /// `hd` must point at a registered host device whose private area holds a pointer to a live
+ /// `T`, and `msg` must point at a valid message.
+ unsafe extern "C" fn message_send(
+ hd: *mut bindings::gb_host_device,
+ dest_cport_id: u16,
+ msg: *mut bindings::gb_message,
+ gfp_mask: bindings::gfp_t,
+ ) -> c_int {
+ // SAFETY: `gb_host_device` and `HostDevice` have the same layout.
+ let hd = unsafe { Device::<device::CoreInternal<'_>>::from_raw(hd) };
+ // SAFETY: `message_send` is only ever called after a successful call to
+ // `gb_hd_add`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { hd.as_ref().drvdata_borrow() };
+ // SAFETY: The caller guarantees `msg` is valid for the duration of this call.
+ let msg = unsafe { Message::from_raw(msg) };
+
+ match T::message_send(&data, dest_cport_id, msg, Flags::new(gfp_mask)) {
+ Ok(()) => {
+ // SAFETY: `msg` is still valid since `sent` is not called anywhere else.
+ unsafe { msg.sent(0) };
+ 0
+ }
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `msg` must point at a valid message of a registered host device of this driver.
+ unsafe extern "C" fn message_cancel(msg: *mut bindings::gb_message) {
+ // SAFETY: The caller guarantees `msg` is valid for the duration of this call.
+ let msg = unsafe { Message::from_raw(msg) };
+ T::message_cancel(msg);
+ }
+}
+
+/// A Greybus host device.
+///
+/// # Invariants
+///
+/// The wrapped value is a valid `struct gb_host_device` created by `gb_hd_create()`, and every
+/// [`ARef<HostDevice>`] owns an increment on its reference count.
+#[repr(transparent)]
+pub struct Device<Ctx = device::Normal> {
+ ptr: Opaque<bindings::gb_host_device>,
+ _ctx: PhantomData<Ctx>,
+}
+
+// SAFETY: `gb_host_device` is reference counted through its embedded `struct device`, which may be
+// used from any thread.
+unsafe impl<Ctx> Send for Device<Ctx> {}
+
+// SAFETY: `gb_host_device` has its own internal locking, so it is safe to share references to it
+// across threads.
+unsafe impl<Ctx> Sync for Device<Ctx> {}
+
+// SAFETY: The embedded `struct device` carries the reference count, and `gb_hd_put()` is just
+// `put_device()` on it, so the object stays alive for as long as increments are outstanding.
+unsafe impl<Ctx> AlwaysRefCounted for Device<Ctx> {
+ #[inline]
+ fn inc_ref(&self) {
+ // SAFETY: By the type invariant there is a live reference to the host device, and `dev` is
+ // its embedded `struct device`.
+ unsafe { bindings::get_device(&raw mut (*self.ptr.get()).dev) };
+ }
+
+ #[inline]
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: The caller guarantees it owns an increment on the reference count.
+ unsafe { bindings::gb_hd_put(obj.as_ptr().cast()) }
+ }
+}
+
+impl<Ctx> Device<Ctx> {
+ /// # Safety
+ ///
+ /// `ptr` must be a valid pointer to a `struct gb_host_device`.
+ #[inline]
+ pub(crate) const unsafe fn from_raw<'a>(ptr: *mut bindings::gb_host_device) -> &'a Self {
+ // SAFETY: `Device` is a transparent wrapper of `Opaque<bindings::gb_host_device>`.
+ unsafe { &*ptr.cast() }
+ }
+
+ #[inline]
+ pub(crate) fn as_raw(&self) -> *mut bindings::gb_host_device {
+ self.ptr.get()
+ }
+
+ /// Hands a message received on `hd_cport_id` to the Greybus core.
+ #[inline]
+ pub fn data_rcvd(&self, cport_id: u16, msg: &[u8]) {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct gb_host_device`. `msg` is valid for reads of ``msg.len()` bytes for the duration
+ // of the call, and the core only reads through the pointer — it copies the payload into
+ // the operation before returning — so handing it a `*mut` derived from a shared reference
+ // is sound.
+ //
+ // TODO: The C signature of this function should be changed to take const pointer for msg.
+ unsafe {
+ bindings::greybus_data_rcvd(self.as_raw(), cport_id, msg.as_ptr().cast_mut(), msg.len())
+ }
+ }
+
+ /// Looks up the connection bound to `cport` on interface `id`.
+ pub fn find_connection_by_intf(&self, id: u8, cport: u16) -> Option<ARef<Connection>> {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct gb_host_device`.
+ let ptr = NonNull::new(unsafe {
+ bindings::gb_connection_hd_find_by_intf(self.as_raw(), id, cport)
+ })?;
+
+ // SAFETY: ptr is a valid gb_connection
+ Some(unsafe { ARef::from_raw(ptr.cast()) })
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
+ #[inline]
+ fn as_ref(&self) -> &device::Device<Ctx> {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct gb_host_device`. `dev` points to a valid `struct device`.
+ unsafe { device::Device::from_raw(addr_of_mut!((*self.as_raw()).dev)) }
+ }
+}
+
+/// A host device owned by its driver, together with the driver's private data `T`.
+///
+/// Created and added to the Greybus bus on construction, removed on drop.
+///
+/// # Invariants
+///
+/// `ptr` points at a valid `struct gb_host_device` obtained from `gb_hd_create()`, whose driver
+/// data holds a live `T`
+#[repr(transparent)]
+pub struct Registration<T> {
+ ptr: NonNull<bindings::gb_host_device>,
+ _data: PhantomData<T>,
+}
+
+impl<T: HdDriver> Registration<T> {
+ /// Creates a host device under `parent` and adds it to the Greybus bus.
+ ///
+ /// `buffer_size_max` is the largest message the transport can carry in one go, header
+ /// included. `num_cports` is the number of cports that the greybus host device can connect to.
+ ///
+ /// `data` is initialised in place before the device is added, so callbacks may run against it
+ /// from the moment `gb_hd_add()` succeeds.
+ pub fn new(
+ parent: &device::Device,
+ buffer_size_max: usize,
+ num_cports: usize,
+ data: impl PinInit<T, Error>,
+ ) -> Result<Self> {
+ // SAFETY: `parent` is a valid device, and the driver table is `'static`. The core only
+ // ever reads through the driver pointer, so casting away `const` is fine.
+ let hd = from_err_ptr(unsafe {
+ bindings::gb_hd_create(
+ core::ptr::from_ref(HdDriverVTable::<T>::build()).cast_mut(),
+ parent.as_raw(),
+ buffer_size_max,
+ num_cports,
+ )
+ })?;
+
+ // SAFETY: `hd` is a valid pointer to `gb_host_device`.
+ let hd_dev: &Device<device::CoreInternal<'_>> = unsafe { Device::from_raw(hd) };
+ hd_dev.as_ref().set_drvdata(data)?;
+
+ // SAFETY: By the type invariant the host device is valid, and it has not been added yet.
+ to_result(unsafe { bindings::gb_hd_add(hd) })?;
+
+ // INVARIANT: `gb_hd_create()` returns a valid `gb_host_device` on success,
+ // and we own the reference it returns until `Drop`.
+ Ok(Self {
+ ptr: NonNull::new(hd).ok_or(code::ENOMEM)?,
+ _data: PhantomData,
+ })
+ }
+}
+
+impl<T> Registration<T> {
+ fn as_raw(&self) -> *mut bindings::gb_host_device {
+ self.ptr.as_ptr()
+ }
+}
+
+impl<T> AsRef<Device> for Registration<T> {
+ #[inline]
+ fn as_ref(&self) -> &Device {
+ // SAFETY: By the type invariant the host device is valid.
+ unsafe { Device::from_raw(self.as_raw()) }
+ }
+}
+
+impl<T> Drop for Registration<T> {
+ fn drop<'a>(&'a mut self) {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct gb_host_device`.
+ unsafe { bindings::gb_hd_del(self.as_raw()) }
+
+ {
+ // SAFETY: `hd` is a valid pointer to `gb_host_device`.
+ let hd_dev = unsafe { Device::<device::CoreInternal<'_>>::from_raw(self.as_raw()) };
+ // SAFETY: The driver data was set to a `T` in `Registration::new()` and has not been
+ // taken since, and this is the only place that takes it.
+ drop(unsafe { hd_dev.as_ref().drvdata_obtain::<T>() });
+ }
+
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct gb_host_device`.
+ unsafe { bindings::gb_hd_put(self.as_raw()) };
+ }
+}
+
+// SAFETY: The greybus host device API is thread-safe as guaranteed by the device core, as long as
+// gb_hd_del() is guaranteed to only be called once - which is guaranteed by our type not
+// having Copy/Clone.
+unsafe impl<T: Send> Send for Registration<T> {}
+
+// SAFETY: The greybus device API is thread-safe as guaranteed by the device core, as long as
+// gb_hd_del() is guaranteed to only be called once - which is guaranteed by our type not
+// having Copy/Clone.
+unsafe impl<T: Sync> Sync for Registration<T> {}
diff --git a/rust/kernel/greybus/mod.rs b/rust/kernel/greybus/mod.rs
new file mode 100644
index 000000000000..791aac38ec29
--- /dev/null
+++ b/rust/kernel/greybus/mod.rs
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for the Greybus core.
+//!
+//! C header: [`include/linux/greybus.h`](srctree/include/linux/greybus.h)
+//!
+//! Greybus host drivers ("host devices") bridge the Greybus core to whatever transport actually
+//! carries the traffic. A host driver creates a [`HostDevice`], registers it, and implements
+//! [`HdDriver`] to transmit the messages the core hands it.
+
+use core::ptr::NonNull;
+
+use crate::{
+ sync::aref::AlwaysRefCounted,
+ transmute::FromBytes,
+ types::Opaque, //
+};
+
+pub mod hd;
+pub mod protocols;
+
+/// The largest Greybus message, header included.
+///
+/// Bounded by the 16-bit `size` field in the operation header.
+pub const GB_OPERATION_SIZE_MAX: usize = u16::MAX as usize;
+
+/// The largest valid CPort id.
+///
+/// Ids above this are reserved by the protocol; a host device's `num_cports` cannot exceed
+/// `CPORT_ID_MAX + 1`.
+pub const CPORT_ID_MAX: usize = bindings::CPORT_ID_MAX as usize;
+
+/// A Greybus message handed to a host driver for transmission.
+///
+/// # Invariants
+///
+/// The shared reference is only ever handed out for the duration of a [`HdDriver`] callback, during
+/// which the Greybus core guarantees the message and its buffer stay alive.
+#[repr(transparent)]
+pub struct Message(Opaque<bindings::gb_message>);
+
+impl Message {
+ /// # Safety
+ ///
+ /// `ptr` must be non-null and point at a valid `struct gb_message` which outlives the
+ /// returned `Self`.
+ #[inline]
+ pub(crate) const unsafe fn from_raw<'a>(ptr: *mut bindings::gb_message) -> &'a Self {
+ // SAFETY: `Message` is a transparent wrapper of `Opaque<bindings::gb_message>`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns the operation header at the start of the message.
+ #[inline]
+ pub const fn header(&self) -> &protocols::GbOperationMsgHdr {
+ // SAFETY: `header` points at a valid `gb_operation_msg_hdr` for as long as the message is
+ // alive, and `GbOperationMsgHdr` is a transparent wrapper of it.
+ unsafe { &*(*self.0.get()).header.cast() }
+ }
+
+ /// # Safety
+ ///
+ /// `Message` is not valid after this call.
+ #[inline]
+ pub(crate) unsafe fn sent(&self, status: i32) {
+ // SAFETY: By the type invariant the message is valid and still owned by the transport,
+ // and its connection and host device are alive for as long as it is.
+ unsafe {
+ bindings::greybus_message_sent(
+ self.operation().connection().host_device().as_raw(),
+ self.0.get(),
+ status,
+ );
+ }
+ }
+
+ /// Returns the payload, i.e. the message without its operation header.
+ pub const fn payload_bytes(&self) -> &[u8] {
+ // SAFETY: By the type invariant the message is valid.
+ let payload = unsafe { (*self.0.get()).payload };
+ // SAFETY: By the type invariant the message is valid.
+ let payload_size = unsafe { (*self.0.get()).payload_size };
+
+ if payload.is_null() || payload_size == 0 {
+ return &[];
+ }
+
+ // SAFETY: A non-null `payload` points at `payload_size` initialized bytes.
+ unsafe { core::slice::from_raw_parts(payload.cast::<u8>(), payload_size) }
+ }
+
+ /// Interprets the message payload as a `T`.
+ ///
+ /// Returns `None` if the payload is too short or misaligned for `T`.
+ #[inline]
+ pub fn payload<T: FromBytes>(&self) -> Option<&T> {
+ T::from_bytes(self.payload_bytes())
+ }
+
+ /// Returns the operation this message belongs to.
+ #[inline]
+ pub const fn operation(&self) -> &Operation {
+ // SAFETY: By the type invariant the message is valid, and its `operation` is set for as
+ // long as the message is alive.
+ unsafe { Operation::from_raw((*self.0.get()).operation) }
+ }
+}
+
+/// A Greybus operation.
+///
+/// # Invariants
+///
+/// The wrapped value is a valid `struct gb_operation`.
+#[repr(transparent)]
+pub struct Operation(Opaque<bindings::gb_operation>);
+
+impl Operation {
+ /// # Safety
+ ///
+ /// `ptr` must be a valid pointer to a `struct gb_operation`.
+ #[inline]
+ pub(crate) const unsafe fn from_raw<'a>(ptr: *mut bindings::gb_operation) -> &'a Self {
+ // SAFETY: `Operation` is a transparent wrapper of `Opaque<bindings::gb_operation>`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns the connection this operation travels on.
+ #[inline]
+ pub const fn connection(&self) -> &Connection {
+ // SAFETY: By the type invariant the operation is valid, and its `connection` is alive for
+ // as long as the operation is.
+ unsafe { Connection::from_raw((*self.0.get()).connection) }
+ }
+}
+
+/// A Greybus connection.
+///
+/// # Invariants
+///
+/// The wrapped value is a valid `struct gb_connection`.
+#[repr(transparent)]
+pub struct Connection(Opaque<bindings::gb_connection>);
+
+// SAFETY: `gb_connection_put()` drops the reference acquired by `gb_connection_get()`, so the
+// connection stays alive for as long as increments are outstanding.
+unsafe impl AlwaysRefCounted for Connection {
+ #[inline]
+ fn inc_ref(&self) {
+ // SAFETY: By the type invariant there is a live reference to the connection.
+ unsafe { bindings::gb_connection_get(self.0.get()) }
+ }
+
+ #[inline]
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: The caller guarantees it owns an increment on the reference count.
+ unsafe { bindings::gb_connection_put(obj.as_ptr().cast()) }
+ }
+}
+
+impl Connection {
+ /// # Safety
+ ///
+ /// `ptr` must be a valid pointer to a `struct gb_connection`.
+ #[inline]
+ pub(crate) const unsafe fn from_raw<'a>(ptr: *mut bindings::gb_connection) -> &'a Self {
+ // SAFETY: `Connection` is a transparent wrapper of `Opaque<bindings::gb_connection>`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns the interface at the far end of the connection. In cases like SVC connection,
+ /// interface can be NULL.
+ #[inline]
+ pub const fn interface(&self) -> Option<&Interface> {
+ // SAFETY: By the type invariant the connection is valid.
+ let intf_ptr = unsafe { (*self.0.get()).intf };
+
+ if intf_ptr.is_null() {
+ None
+ } else {
+ // SAFETY: By the previous check, intf_ptr is valid.
+ Some(unsafe { Interface::from_raw(intf_ptr) })
+ }
+ }
+
+ /// Returns the CPort id this connection uses on the interface.
+ #[inline]
+ pub const fn intf_cport_id(&self) -> u16 {
+ // SAFETY: By the type invariant the connection is valid.
+ unsafe { (*self.0.get()).intf_cport_id }
+ }
+
+ /// Returns the CPort id this connection uses on the host device.
+ #[inline]
+ pub const fn hd_cport_id(&self) -> u16 {
+ // SAFETY: By the type invariant the connection is valid.
+ unsafe { (*self.0.get()).hd_cport_id }
+ }
+
+ /// Returns the host device this connection belongs to.
+ #[inline]
+ pub const fn host_device(&self) -> &hd::Device {
+ // SAFETY: By the type invariant the connection is valid, and its `hd` is alive for as
+ // long as the connection is.
+ unsafe { hd::Device::from_raw((*self.0.get()).hd) }
+ }
+}
+
+/// A Greybus interface.
+///
+/// # Invariants
+///
+/// The wrapped value is a valid `struct gb_interface`.
+#[repr(transparent)]
+pub struct Interface(Opaque<bindings::gb_interface>);
+
+impl Interface {
+ /// # Safety
+ ///
+ /// `ptr` must be a valid pointer to a `struct gb_interface`.
+ #[inline]
+ pub(crate) const unsafe fn from_raw<'a>(ptr: *mut bindings::gb_interface) -> &'a Self {
+ // SAFETY: `Interface` is a transparent wrapper of `Opaque<bindings::gb_interface>`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns the interface id, unique within its host device.
+ #[inline]
+ pub const fn id(&self) -> u8 {
+ // SAFETY: By the type invariant the interface is valid.
+ unsafe { (*self.0.get()).interface_id }
+ }
+}
diff --git a/rust/kernel/greybus/protocols.rs b/rust/kernel/greybus/protocols.rs
new file mode 100644
index 000000000000..4658ace5e323
--- /dev/null
+++ b/rust/kernel/greybus/protocols.rs
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Greybus wire format definitions.
+//!
+//! Thin `repr(transparent)` wrappers over the generated bindings for the operation header and the
+//! SVC protocol messages, plus the constants that go in their type and result fields. The
+//! wrappers exist to keep the byte-order conversions in one place: constructors take native-endian
+//! values and store little-endian, accessors convert back.
+
+/// Set in the header type field to mark a message as a response to the operation of the same id.
+pub const MESSAGE_TYPE_RESPONSE: u8 = 0x80;
+
+/// The CPort id reserved for the SVC connection on every host device.
+pub const GB_SVC_CPORT_ID: u16 = bindings::GB_SVC_CPORT_ID as u16;
+
+/// Defines `u8` constants from same-named `bindings` values.
+macro_rules! gb_u8_consts {
+ ($($name:ident),* $(,)?) => {
+ $(
+ #[allow(missing_docs)]
+ pub const $name: u8 = bindings::$name as u8;
+ )*
+ };
+}
+
+// SVC Operation Types
+gb_u8_consts! {
+ GB_SVC_TYPE_PROTOCOL_VERSION,
+ GB_SVC_TYPE_SVC_HELLO,
+ GB_SVC_TYPE_INTF_DEVICE_ID,
+ GB_SVC_TYPE_INTF_RESET,
+ GB_SVC_TYPE_CONN_CREATE,
+ GB_SVC_TYPE_CONN_DESTROY,
+ GB_SVC_TYPE_DME_PEER_GET,
+ GB_SVC_TYPE_DME_PEER_SET,
+ GB_SVC_TYPE_ROUTE_CREATE,
+ GB_SVC_TYPE_ROUTE_DESTROY,
+ GB_SVC_TYPE_TIMESYNC_ENABLE,
+ GB_SVC_TYPE_TIMESYNC_DISABLE,
+ GB_SVC_TYPE_TIMESYNC_AUTHORITATIVE,
+ GB_SVC_TYPE_INTF_SET_PWRM,
+ GB_SVC_TYPE_INTF_EJECT,
+ GB_SVC_TYPE_PING,
+ GB_SVC_TYPE_PWRMON_RAIL_COUNT_GET,
+ GB_SVC_TYPE_PWRMON_RAIL_NAMES_GET,
+ GB_SVC_TYPE_PWRMON_SAMPLE_GET,
+ GB_SVC_TYPE_PWRMON_INTF_SAMPLE_GET,
+ GB_SVC_TYPE_TIMESYNC_WAKE_PINS_ACQUIRE,
+ GB_SVC_TYPE_TIMESYNC_WAKE_PINS_RELEASE,
+ GB_SVC_TYPE_TIMESYNC_PING,
+ GB_SVC_TYPE_MODULE_INSERTED,
+ GB_SVC_TYPE_MODULE_REMOVED,
+ GB_SVC_TYPE_INTF_VSYS_ENABLE,
+ GB_SVC_TYPE_INTF_VSYS_DISABLE,
+ GB_SVC_TYPE_INTF_REFCLK_ENABLE,
+ GB_SVC_TYPE_INTF_REFCLK_DISABLE,
+ GB_SVC_TYPE_INTF_UNIPRO_ENABLE,
+ GB_SVC_TYPE_INTF_UNIPRO_DISABLE,
+ GB_SVC_TYPE_INTF_ACTIVATE,
+ GB_SVC_TYPE_INTF_RESUME,
+ GB_SVC_TYPE_INTF_MAILBOX_EVENT,
+ GB_SVC_TYPE_INTF_OOPS,
+}
+
+// UNIPRO modes
+gb_u8_consts! {
+ GB_SVC_UNIPRO_FAST_MODE,
+ GB_SVC_UNIPRO_SLOW_MODE,
+ GB_SVC_UNIPRO_FAST_AUTO_MODE,
+ GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+ GB_SVC_UNIPRO_MODE_UNCHANGED,
+ GB_SVC_UNIPRO_HIBERNATE_MODE,
+ GB_SVC_UNIPRO_OFF_MODE,
+}
+
+// PWR States
+gb_u8_consts! {
+ GB_SVC_SETPWRM_PWR_OK,
+ GB_SVC_SETPWRM_PWR_LOCAL,
+ GB_SVC_SETPWRM_PWR_REMOTE,
+ GB_SVC_SETPWRM_PWR_BUSY,
+ GB_SVC_SETPWRM_PWR_ERROR_CAP,
+ GB_SVC_SETPWRM_PWR_FATAL_ERROR,
+}
+
+// Vsys Result
+gb_u8_consts! {
+ GB_SVC_INTF_VSYS_OK,
+ GB_SVC_INTF_VSYS_FAIL,
+}
+
+// Refclk Result
+gb_u8_consts! {
+ GB_SVC_INTF_REFCLK_OK,
+ GB_SVC_INTF_REFCLK_FAIL,
+}
+
+// Unipro Result
+gb_u8_consts! {
+ GB_SVC_INTF_UNIPRO_OK,
+ GB_SVC_INTF_UNIPRO_FAIL,
+ GB_SVC_INTF_UNIPRO_NOT_OFF,
+}
+
+// Op Codes
+gb_u8_consts! {
+ GB_SVC_OP_SUCCESS,
+ GB_SVC_OP_UNKNOWN_ERROR,
+ GB_SVC_INTF_NOT_DETECTED,
+ GB_SVC_INTF_NO_UPRO_LINK,
+ GB_SVC_INTF_UPRO_NOT_DOWN,
+ GB_SVC_INTF_UPRO_NOT_HIBERNATED,
+ GB_SVC_INTF_NO_V_SYS,
+ GB_SVC_INTF_V_CHG,
+ GB_SVC_INTF_WAKE_BUSY,
+ GB_SVC_INTF_NO_REFCLK,
+ GB_SVC_INTF_RELEASING,
+ GB_SVC_INTF_NO_ORDER,
+ GB_SVC_INTF_MBOX_SET,
+ GB_SVC_INTF_BAD_MBOX,
+ GB_SVC_INTF_OP_TIMEOUT,
+ GB_SVC_PWRMON_OP_NOT_PRESENT,
+}
+
+// Greybus Interface Types
+gb_u8_consts! {
+ GB_SVC_INTF_TYPE_UNKNOWN,
+ GB_SVC_INTF_TYPE_DUMMY,
+ GB_SVC_INTF_TYPE_UNIPRO,
+ GB_SVC_INTF_TYPE_GREYBUS,
+}
+
+/// The header every Greybus message starts with.
+///
+/// # Invariants
+///
+/// The `size` field covers the header and the payload that follows it.
+#[repr(transparent)]
+pub struct GbOperationMsgHdr(bindings::gb_operation_msg_hdr);
+
+// SAFETY: `gb_operation_msg_hdr` is a POD type with no padding and no interior mutability.
+unsafe impl kernel::transmute::AsBytes for GbOperationMsgHdr {}
+
+impl GbOperationMsgHdr {
+ /// Builds a header. `size` is the whole message, this header included.
+ #[inline]
+ pub const fn new(size: u16, operation_id: u16, type_: u8, result: u8) -> Self {
+ Self(bindings::gb_operation_msg_hdr {
+ size: size.to_le(),
+ operation_id: operation_id.to_le(),
+ type_,
+ result,
+ pad: [0u8; 2],
+ })
+ }
+
+ /// Returns the type field, response bit included.
+ #[inline]
+ pub const fn msg_type(&self) -> u8 {
+ self.0.type_
+ }
+
+ /// Returns whether this is a response rather than a request.
+ #[inline]
+ pub const fn is_response(&self) -> bool {
+ self.0.type_ & MESSAGE_TYPE_RESPONSE != 0
+ }
+
+ /// Returns the operation id pairing a response with its request. Zero for unidirectional
+ /// messages.
+ #[inline]
+ pub const fn operation_id(&self) -> u16 {
+ u16::from_le(self.0.operation_id)
+ }
+
+ /// Returns the type field with the response bit cleared.
+ #[inline]
+ pub const fn request_type(&self) -> u8 {
+ self.msg_type() & !MESSAGE_TYPE_RESPONSE
+ }
+
+ /// Returns the whole message size, this header included.
+ #[inline]
+ pub const fn size(&self) -> u16 {
+ u16::from_le(self.0.size)
+ }
+}
+
+/// Request for [`GB_SVC_TYPE_PROTOCOL_VERSION`].
+#[repr(transparent)]
+pub struct GbSvcVersionRequest(bindings::gb_svc_version_request);
+
+impl GbSvcVersionRequest {
+ /// Creates a request advertising SVC protocol version `major`.`minor`.
+ #[inline]
+ pub const fn new(major: u8, minor: u8) -> Self {
+ Self(bindings::gb_svc_version_request { major, minor })
+ }
+}
+
+/// Request for [`GB_SVC_TYPE_SVC_HELLO`].
+#[repr(transparent)]
+pub struct GbSvcHelloRequest(bindings::gb_svc_hello_request);
+
+impl GbSvcHelloRequest {
+ /// Creates a hello request identifying the endo as `endo_id` and the AP's own interface as
+ /// `interface_id`.
+ #[inline]
+ pub const fn new(endo_id: u16, interface_id: u8) -> Self {
+ Self(bindings::gb_svc_hello_request {
+ endo_id: endo_id.to_le(),
+ interface_id,
+ })
+ }
+}
+
+/// Request for [`GB_SVC_TYPE_INTF_SET_PWRM`].
+#[repr(transparent)]
+pub struct GbSvcIntfSetPwrmRequest(bindings::gb_svc_intf_set_pwrm_request);
+
+// SAFETY: `gb_svc_intf_set_pwrm_request` is a struct of `u8` fields, so every bit pattern of its
+// size is a valid instance.
+unsafe impl kernel::transmute::FromBytes for GbSvcIntfSetPwrmRequest {}
+
+impl GbSvcIntfSetPwrmRequest {
+ /// Returns the requested TX gear, one of the `GB_SVC_UNIPRO_*` modes.
+ #[inline]
+ pub const fn tx_mode(&self) -> u8 {
+ self.0.tx_mode
+ }
+
+ /// Returns the requested RX gear, one of the `GB_SVC_UNIPRO_*` modes.
+ #[inline]
+ pub const fn rx_mode(&self) -> u8 {
+ self.0.rx_mode
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_INTF_SET_PWRM`].
+#[repr(transparent)]
+pub struct GbSvcIntfSetPwrmResponse(bindings::gb_svc_intf_set_pwrm_response);
+
+impl GbSvcIntfSetPwrmResponse {
+ /// `result_code` is one of the `GB_SVC_SETPWRM_PWR_*` codes.
+ #[inline]
+ pub const fn new(result_code: u8) -> Self {
+ Self(bindings::gb_svc_intf_set_pwrm_response { result_code })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_DME_PEER_GET`].
+#[repr(transparent)]
+pub struct GbSvcDmePeerGetResponse(bindings::gb_svc_dme_peer_get_response);
+
+impl GbSvcDmePeerGetResponse {
+ /// `result_code` is the UniPro `ConfigResultCode`; `attr_value` is the UniPro attribute
+ /// value.
+ #[inline]
+ pub const fn new(result_code: u16, attr_value: u32) -> Self {
+ Self(bindings::gb_svc_dme_peer_get_response {
+ result_code: result_code.to_le(),
+ attr_value: attr_value.to_le(),
+ })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_DME_PEER_SET`].
+#[repr(transparent)]
+pub struct GbSvcDmePeerSetResponse(bindings::gb_svc_dme_peer_set_response);
+
+impl GbSvcDmePeerSetResponse {
+ /// `result_code` is the UniPro `ConfigResultCode`.
+ #[inline]
+ pub const fn new(result_code: u16) -> Self {
+ Self(bindings::gb_svc_dme_peer_set_response {
+ result_code: result_code.to_le(),
+ })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_PWRMON_RAIL_COUNT_GET`].
+#[repr(transparent)]
+pub struct GbSvcPwrmonRailCountGetResponse(bindings::gb_svc_pwrmon_rail_count_get_response);
+
+impl GbSvcPwrmonRailCountGetResponse {
+ /// Creates a response reporting `rail_count` available rails.
+ #[inline]
+ pub const fn new(rail_count: u8) -> Self {
+ Self(bindings::gb_svc_pwrmon_rail_count_get_response { rail_count })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_INTF_VSYS_ENABLE`] and [`GB_SVC_TYPE_INTF_VSYS_DISABLE`].
+#[repr(transparent)]
+pub struct GbSvcIntfVsysResponse(bindings::gb_svc_intf_vsys_response);
+
+impl GbSvcIntfVsysResponse {
+ /// `result_code` is [`GB_SVC_INTF_VSYS_OK`] or [`GB_SVC_INTF_VSYS_FAIL`].
+ #[inline]
+ pub const fn new(result_code: u8) -> Self {
+ Self(bindings::gb_svc_intf_vsys_response { result_code })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_INTF_REFCLK_ENABLE`] and [`GB_SVC_TYPE_INTF_REFCLK_DISABLE`].
+#[repr(transparent)]
+pub struct GbSvcIntfRefclkResponse(bindings::gb_svc_intf_refclk_response);
+
+impl GbSvcIntfRefclkResponse {
+ /// `result_code` is [`GB_SVC_INTF_REFCLK_OK`] or [`GB_SVC_INTF_REFCLK_FAIL`].
+ #[inline]
+ pub const fn new(result_code: u8) -> Self {
+ Self(bindings::gb_svc_intf_refclk_response { result_code })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_INTF_UNIPRO_ENABLE`] and [`GB_SVC_TYPE_INTF_UNIPRO_DISABLE`].
+#[repr(transparent)]
+pub struct GbSvcIntfUniproResponse(bindings::gb_svc_intf_unipro_response);
+
+impl GbSvcIntfUniproResponse {
+ /// `result_code` is one of the `GB_SVC_INTF_UNIPRO_*` codes.
+ #[inline]
+ pub const fn new(result_code: u8) -> Self {
+ Self(bindings::gb_svc_intf_unipro_response { result_code })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_INTF_ACTIVATE`].
+#[repr(transparent)]
+pub struct GbSvcIntfActivateResponse(bindings::gb_svc_intf_activate_response);
+
+impl GbSvcIntfActivateResponse {
+ /// `status` is one of the `GB_SVC_OP_*` codes; `intf_type` is one of the
+ /// `GB_SVC_INTF_TYPE_*` values and is only meaningful when `status` is
+ /// [`GB_SVC_OP_SUCCESS`].
+ #[inline]
+ pub const fn new(status: u8, intf_type: u8) -> Self {
+ Self(bindings::gb_svc_intf_activate_response { status, intf_type })
+ }
+}
+
+/// Response to [`GB_SVC_TYPE_INTF_RESUME`].
+#[repr(transparent)]
+pub struct GbSvcIntfResumeResponse(bindings::gb_svc_intf_resume_response);
+
+impl GbSvcIntfResumeResponse {
+ /// `status` is one of the `GB_SVC_OP_*` codes.
+ #[inline]
+ pub const fn new(status: u8) -> Self {
+ Self(bindings::gb_svc_intf_resume_response { status })
+ }
+}
+
+/// Request for [`GB_SVC_TYPE_MODULE_INSERTED`].
+#[repr(transparent)]
+pub struct GbSvcModuleInsertedRequest(bindings::gb_svc_module_inserted_request);
+
+impl GbSvcModuleInsertedRequest {
+ /// The module spans `intf_count` consecutive interfaces starting at `primary_intf_id`.
+ /// `flags` is a mask of `GB_SVC_MODULE_INSERTED_FLAG_*` values.
+ #[inline]
+ pub const fn new(primary_intf_id: u8, intf_count: u8, flags: u16) -> Self {
+ Self(bindings::gb_svc_module_inserted_request {
+ primary_intf_id,
+ intf_count,
+ flags: flags.to_le(),
+ })
+ }
+}
+
+/// Request for [`GB_SVC_TYPE_MODULE_REMOVED`].
+#[repr(transparent)]
+pub struct GbSvcModuleRemovedRequest(bindings::gb_svc_module_removed_request);
+
+// SAFETY: `gb_svc_module_removed_request` is a struct of `u8` field, so every bit pattern of its
+// size is a valid instance.
+unsafe impl kernel::transmute::FromBytes for GbSvcModuleRemovedRequest {}
+
+impl GbSvcModuleRemovedRequest {
+ /// `primary_intf_id` identifies the module, and matches the one given when it was inserted.
+ #[inline]
+ pub const fn new(primary_intf_id: u8) -> Self {
+ Self(bindings::gb_svc_module_removed_request { primary_intf_id })
+ }
+
+ /// Returns the primary_intf_id field.
+ #[inline]
+ pub const fn primary_intf_id(&self) -> u8 {
+ self.0.primary_intf_id
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 45c4f4db51d2..fca4f1795fea 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -80,6 +80,8 @@
pub mod fs;
#[cfg(CONFIG_GPU_BUDDY = "y")]
pub mod gpu;
+#[cfg(CONFIG_RUST_GREYBUS_ABSTRACTIONS)]
+pub mod greybus;
#[cfg(CONFIG_I2C = "y")]
pub mod i2c;
pub mod id_pool;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 5/7] drivers: greybus: Add software SVC implementation
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
` (3 preceding siblings ...)
2026-08-27 7:54 ` [PATCH v2 4/7] rust: kernel: Add greybus abstractions Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 8:09 ` sashiko-bot
2026-08-27 7:54 ` [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom Ayush Singh
2026-08-27 7:54 ` [PATCH v2 7/7] greybus: Add Rust UART node driver Ayush Singh
6 siblings, 1 reply; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
A Greybus network requires an SVC (Supervisory Controller) to bring
interfaces up, assign device IDs and connect CPorts to the AP. On a
UniPro network the SVC is a real entity on the bus, but transports that
merely carry Greybus messages - a UART, an I2C bus, a network link -
have no such entity, so every host driver for those transports ends up
open-coding an SVC emulation of its own.
Add gb-softsvc, a Rust implementation of the SVC that lives in the
kernel and registers itself as a Greybus host device. It answers the
SVC-side operations the core expects during interface bring-up and
teardown.
Host drivers use it through a small in-kernel interface. A driver
implements the NodeOps trait to send data towards its node and then
calls module_insert() to announce a new node, submit_message() to hand
incoming Greybus messages back to the core, and module_remove() on
disconnect. Interface IDs are allocated by the SVC and returned from
module_insert().
Connection create/destroy and interface activate/resume currently just
acknowledge the request; callbacks into NodeOps can be added later if
transports need to act on them.
gb_softsvc_exports.c exports the Rust symbols for dependent modules, as
the build system does not yet support Rust cross-module dependencies
natively. This hack is taken from nova-core [0].
[0]: https://lore.kernel.org/all/20260622-nova-exports-v5-0-6191773fc977@nvidia.com/
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
MAINTAINERS | 6 +
drivers/greybus/Kconfig | 12 +
drivers/greybus/Makefile | 2 +
drivers/greybus/gb_softsvc.rs | 504 +++++++++++++++++++++++++++++++++++
drivers/greybus/gb_softsvc_exports.c | 15 ++
5 files changed, 539 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 80247a031353..3ba56cdbf056 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11328,6 +11328,12 @@ S: Maintained
F: Documentation/devicetree/bindings/net/ti,cc1352p7.yaml
F: drivers/greybus/gb-beagleplay.c
+GREYBUS SOFTSVC DRIVERS
+M: Ayush Singh <ayush@beagleboard.com>
+L: greybus-dev@lists.linaro.org (moderated for non-subscribers)
+S: Maintained
+F: drivers/greybus/gb_softsvc.rs
+
GREYBUS SUBSYSTEM
M: Johan Hovold <johan@kernel.org>
M: Alex Elder <elder@kernel.org>
diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
index 3bf3b8691442..381d1a6ee135 100644
--- a/drivers/greybus/Kconfig
+++ b/drivers/greybus/Kconfig
@@ -48,5 +48,17 @@ config GREYBUS_ES2
To compile this code as a module, choose M here: the module
will be called gb-es2.ko
+config GREYBUS_SOFTSVC
+ tristate "Greybus software SVC"
+ depends on RUST
+ help
+ Select this option to enable a software implementation of the
+ Greybus SVC (Supervisory Controller) running in the kernel. It can
+ be used by host drivers that talk to a Greybus network which does
+ not provide an SVC of its own.
+
+ To compile this code as a module, choose M here: the module
+ will be called gb-softsvc.ko
+
endif # GREYBUS
diff --git a/drivers/greybus/Makefile b/drivers/greybus/Makefile
index d986e94f8897..e6f594128802 100644
--- a/drivers/greybus/Makefile
+++ b/drivers/greybus/Makefile
@@ -25,4 +25,6 @@ gb-es2-y := es2.o
obj-$(CONFIG_GREYBUS_ES2) += gb-es2.o
+obj-$(CONFIG_GREYBUS_SOFTSVC) += gb-softsvc.o
+gb-softsvc-y += gb_softsvc.o gb_softsvc_exports.o
diff --git a/drivers/greybus/gb_softsvc.rs b/drivers/greybus/gb_softsvc.rs
new file mode 100644
index 000000000000..79e2752e42ba
--- /dev/null
+++ b/drivers/greybus/gb_softsvc.rs
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Greybus software SVC.
+//!
+//! A Greybus network needs an SVC to bring interfaces up and to connect their CPorts to the AP.
+//! Transports that carry Greybus without one - a UART or an I2C bus rather than a UniPro network -
+//! have to provide it themselves.
+//!
+//! This module implements that SVC in software.
+
+use kernel::{
+ alloc::Flags,
+ c_str,
+ error::code,
+ faux,
+ greybus::{
+ self,
+ hd,
+ protocols,
+ CPORT_ID_MAX,
+ GB_OPERATION_SIZE_MAX, //
+ },
+ id_pool::IdPool,
+ new_mutex, new_spinlock,
+ prelude::*,
+ sync::{
+ aref::ARef,
+ Arc,
+ Mutex,
+ SpinLock, //
+ },
+ transmute::AsBytes,
+};
+use pin_init::init_array_from_fn;
+
+const SVC_VERSION_MAJOR: u8 = 0;
+const SVC_VERSION_MINOR: u8 = 1;
+
+const ENDO_ID: u16 = 0;
+const AP_INF_ID: u8 = 0;
+
+const TOSHIBA_DMID: u32 = 0x0126;
+
+const MAX_INTF_ID: u8 = u8::MAX;
+const INTF_ID_START: u8 = 1;
+const INTF_MAP_LEN: usize = (MAX_INTF_ID - INTF_ID_START) as usize;
+
+#[repr(C, packed)]
+struct Msg<P> {
+ hdr: protocols::GbOperationMsgHdr,
+ payload: P,
+}
+
+// SAFETY: `Msg` is a POD type with no padding and no interior mutability.
+unsafe impl<P> kernel::transmute::AsBytes for Msg<P> {}
+
+impl<P> Msg<P> {
+ const GB_SIZE: u16 = size_of::<Self>() as u16;
+
+ /// Builds a request of type `ty`.
+ ///
+ /// An `operation_id` of zero marks a unidirectional request, for which the AP sends no
+ /// response.
+ const fn request(operation_id: u16, ty: u8, payload: P) -> Self {
+ Self {
+ hdr: protocols::GbOperationMsgHdr::new(Self::GB_SIZE, operation_id, ty, 0),
+ payload,
+ }
+ }
+
+ /// Builds the response to `request`, carrying `result`.
+ const fn response(request: &protocols::GbOperationMsgHdr, result: u8, payload: P) -> Self {
+ Self {
+ hdr: protocols::GbOperationMsgHdr::new(
+ Self::GB_SIZE,
+ request.operation_id(),
+ request.msg_type() | protocols::MESSAGE_TYPE_RESPONSE,
+ result,
+ ),
+ payload,
+ }
+ }
+}
+
+const _: () = assert!(size_of::<Msg<()>>() == size_of::<protocols::GbOperationMsgHdr>());
+
+module! {
+ type: GreybusSoftSvc,
+ name: "gb_softsvc",
+ authors: ["Ayush Singh <ayush@beagleboard.org>"],
+ description: "Greybus software SVC implementation",
+ license: "GPL",
+}
+
+kernel::sync::global_lock! {
+ unsafe(uninit) static GLOBAL_STATE: Mutex<Option<GlobalState>> = None;
+}
+
+struct GlobalState {
+ svc: Arc<Svc>,
+ gb_hd: ARef<hd::Device>,
+}
+
+impl GlobalState {
+ fn new(svc: Arc<Svc>, gb_hd: ARef<hd::Device>) -> Self {
+ Self { svc, gb_hd }
+ }
+}
+
+#[pin_data]
+struct Svc {
+ #[pin]
+ svc_operation_id: SpinLock<IdPool>,
+ // Pos 0 = INTF_ID_START
+ #[pin]
+ intf_map: Mutex<[Option<Arc<dyn InterfaceOps>>; INTF_MAP_LEN]>,
+}
+
+impl Svc {
+ fn new() -> impl PinInit<Self, Error> {
+ pin_init!(Self {
+ intf_map <- new_mutex!(init_array_from_fn(|_| None)),
+ svc_operation_id <- new_spinlock!(IdPool::with_capacity(u16::MAX.into(), GFP_KERNEL)?)
+ }? Error)
+ }
+
+ fn new_operation_id(&self) -> Result<u16> {
+ let mut ida = self.svc_operation_id.lock();
+ let res = ida.find_unused_id(1).ok_or(code::EOVERFLOW)?.acquire() as u16;
+
+ Ok(res)
+ }
+
+ fn release_operation_id(&self, id: u16) {
+ let mut ida = self.svc_operation_id.lock();
+ ida.release_id(id as usize);
+ }
+
+ fn map_insert(&self, intfs: &[Arc<dyn InterfaceOps>]) -> Result<u8> {
+ fn inner(
+ intf_map: &[Option<Arc<dyn InterfaceOps>>; INTF_MAP_LEN],
+ count: usize,
+ ) -> Result<u8> {
+ for (id, window) in intf_map.windows(count).enumerate() {
+ if window.iter().all(|x| x.is_none()) {
+ return Ok(id as u8);
+ }
+ }
+
+ Err(code::EOVERFLOW)
+ }
+
+ let mut guard = self.intf_map.lock();
+
+ let pos = inner(&guard, intfs.len())?;
+ for (i, intf) in intfs.iter().cloned().enumerate() {
+ guard[usize::from(pos) + i] = Some(intf);
+ }
+
+ Ok(pos + INTF_ID_START)
+ }
+
+ fn map_remove(&self, primary_id: u8, intf_count: u8) {
+ let id = primary_id - INTF_ID_START;
+ let mut guard = self.intf_map.lock();
+
+ for i in 0..intf_count {
+ let _ = guard[usize::from(id + i)].take();
+ }
+ }
+
+ fn intf_by_id(&self, id: u8) -> Result<Arc<dyn InterfaceOps>> {
+ let id = id - INTF_ID_START;
+ let guard = self.intf_map.lock();
+
+ guard
+ .get(usize::from(id))
+ .ok_or(code::ENODEV)?
+ .as_ref()
+ .ok_or(code::ENODEV)
+ .cloned()
+ }
+
+ fn send_request<P>(&self, hd: &hd::Device, ty: u8, payload: P) -> Result<()> {
+ let msg = Msg::request(self.new_operation_id()?, ty, payload);
+ hd.data_rcvd(protocols::GB_SVC_CPORT_ID, msg.as_bytes());
+ Ok(())
+ }
+
+ fn module_insert(&self, hd: &hd::Device, intfs: &[Arc<dyn InterfaceOps>]) -> Result<u8> {
+ let intf_id = self.map_insert(intfs)?;
+ // map_insert already checks that intf len fits in u8
+ let intf_count = intfs.len() as u8;
+
+ let payload = protocols::GbSvcModuleInsertedRequest::new(intf_id, intf_count, 0);
+ let res = self.send_request(hd, protocols::GB_SVC_TYPE_MODULE_INSERTED, payload);
+
+ if res.is_err() {
+ self.map_remove(intf_id, intf_count);
+ }
+
+ res.map(|()| intf_id)
+ }
+
+ fn module_remove(&self, hd: &hd::Device, intf_id: u8, intf_count: u8) -> Result<()> {
+ self.map_remove(intf_id, intf_count);
+ let payload = protocols::GbSvcModuleRemovedRequest::new(intf_id);
+ self.send_request(hd, protocols::GB_SVC_TYPE_MODULE_REMOVED, payload)?;
+
+ Ok(())
+ }
+
+ fn send_version(&self, hd: &hd::Device) -> Result<()> {
+ dev_info!(hd.as_ref(), "Sending SVC version request");
+
+ let payload = protocols::GbSvcVersionRequest::new(SVC_VERSION_MAJOR, SVC_VERSION_MINOR);
+ self.send_request(hd, protocols::GB_SVC_TYPE_PROTOCOL_VERSION, payload)?;
+
+ Ok(())
+ }
+
+ fn send_svc_hello(&self, hd: &hd::Device) -> Result<()> {
+ dev_info!(hd.as_ref(), "Sending SVC Hello request");
+
+ let payload = protocols::GbSvcHelloRequest::new(ENDO_ID, AP_INF_ID);
+ self.send_request(hd, protocols::GB_SVC_TYPE_SVC_HELLO, payload)?;
+
+ Ok(())
+ }
+
+ fn send_response<P>(&self, hd: &hd::Device, msg: &protocols::GbOperationMsgHdr, payload: P) {
+ let msg = Msg::response(msg, 0, payload);
+ hd.data_rcvd(protocols::GB_SVC_CPORT_ID, msg.as_bytes());
+ }
+
+ fn intf_set_pwrm(&self, hd: &hd::Device, msg: &greybus::Message) -> Result<()> {
+ let req_msg: &protocols::GbSvcIntfSetPwrmRequest = msg.payload().ok_or(code::EINVAL)?;
+ let result_code = if req_msg.tx_mode() == protocols::GB_SVC_UNIPRO_HIBERNATE_MODE
+ && req_msg.rx_mode() == protocols::GB_SVC_UNIPRO_HIBERNATE_MODE
+ {
+ protocols::GB_SVC_SETPWRM_PWR_OK
+ } else {
+ protocols::GB_SVC_SETPWRM_PWR_LOCAL
+ };
+
+ let payload = protocols::GbSvcIntfSetPwrmResponse::new(result_code);
+ self.send_response(hd, msg.header(), payload);
+
+ Ok(())
+ }
+
+ fn dme_peer_get(&self, hd: &hd::Device, hdr: &protocols::GbOperationMsgHdr) {
+ let payload = protocols::GbSvcDmePeerGetResponse::new(0, TOSHIBA_DMID);
+ self.send_response(hd, hdr, payload);
+ }
+
+ fn dme_peer_set(&self, hd: &hd::Device, hdr: &protocols::GbOperationMsgHdr) {
+ let payload = protocols::GbSvcDmePeerSetResponse::new(0);
+ self.send_response(hd, hdr, payload);
+ }
+
+ fn pwrmon_rail_count_get(&self, hd: &hd::Device, hdr: &protocols::GbOperationMsgHdr) {
+ let payload = protocols::GbSvcPwrmonRailCountGetResponse::new(0);
+ self.send_response(hd, hdr, payload);
+ }
+
+ fn intf_vsys_enable_disable(&self, hd: &hd::Device, hdr: &protocols::GbOperationMsgHdr) {
+ let payload = protocols::GbSvcIntfVsysResponse::new(protocols::GB_SVC_INTF_VSYS_OK);
+ self.send_response(hd, hdr, payload);
+ }
+
+ fn intf_refclk_enable_disable(&self, hd: &hd::Device, hdr: &protocols::GbOperationMsgHdr) {
+ let payload = protocols::GbSvcIntfRefclkResponse::new(protocols::GB_SVC_INTF_VSYS_OK);
+ self.send_response(hd, hdr, payload);
+ }
+
+ fn intf_unipro_enable_disable(&self, hd: &hd::Device, hdr: &protocols::GbOperationMsgHdr) {
+ let payload = protocols::GbSvcIntfUniproResponse::new(protocols::GB_SVC_INTF_UNIPRO_OK);
+ self.send_response(hd, hdr, payload);
+ }
+
+ fn intf_activate(&self, hd: &hd::Device, msg: &greybus::Message) {
+ let payload = protocols::GbSvcIntfActivateResponse::new(
+ protocols::GB_SVC_OP_SUCCESS,
+ protocols::GB_SVC_INTF_TYPE_GREYBUS,
+ );
+
+ // TODO: Maybe call a callback?
+
+ self.send_response(hd, msg.header(), payload);
+ }
+
+ fn intf_resume(&self, hd: &hd::Device, msg: &greybus::Message) {
+ let payload = protocols::GbSvcIntfResumeResponse::new(protocols::GB_SVC_OP_SUCCESS);
+
+ // TODO: Maybe call a callback?
+
+ self.send_response(hd, msg.header(), payload);
+ }
+
+ fn conn_create(&self, hd: &hd::Device, msg: &greybus::Message) -> Result<()> {
+ // TODO: Evaluate if we need to add callback to NodeOps
+ self.send_response(hd, msg.header(), ());
+
+ Ok(())
+ }
+
+ fn conn_destroy(&self, hd: &hd::Device, msg: &greybus::Message) -> Result<()> {
+ // TODO: Evaluate if we need to add callback to NodeOps
+ self.send_response(hd, msg.header(), ());
+
+ Ok(())
+ }
+
+ fn handler(&self, msg: &greybus::Message) -> Result<()> {
+ let hdr = msg.header();
+ let hd = msg.operation().connection().host_device();
+
+ if hdr.is_response() {
+ self.release_operation_id(hdr.operation_id());
+ }
+
+ match (hdr.is_response(), hdr.request_type()) {
+ (
+ false,
+ protocols::GB_SVC_TYPE_INTF_DEVICE_ID
+ | protocols::GB_SVC_TYPE_ROUTE_CREATE
+ | protocols::GB_SVC_TYPE_ROUTE_DESTROY
+ | protocols::GB_SVC_TYPE_PING,
+ ) => self.send_response(hd, hdr, ()),
+ (false, protocols::GB_SVC_TYPE_CONN_CREATE) => self.conn_create(hd, msg)?,
+ (false, protocols::GB_SVC_TYPE_CONN_DESTROY) => self.conn_destroy(hd, msg)?,
+ (false, protocols::GB_SVC_TYPE_DME_PEER_GET) => self.dme_peer_get(hd, hdr),
+ (false, protocols::GB_SVC_TYPE_DME_PEER_SET) => self.dme_peer_set(hd, hdr),
+ (false, protocols::GB_SVC_TYPE_INTF_SET_PWRM) => self.intf_set_pwrm(hd, msg)?,
+ (false, protocols::GB_SVC_TYPE_PWRMON_RAIL_COUNT_GET) => {
+ self.pwrmon_rail_count_get(hd, hdr)
+ }
+ (
+ false,
+ protocols::GB_SVC_TYPE_INTF_VSYS_ENABLE | protocols::GB_SVC_TYPE_INTF_VSYS_DISABLE,
+ ) => self.intf_vsys_enable_disable(hd, hdr),
+ (
+ false,
+ protocols::GB_SVC_TYPE_INTF_REFCLK_ENABLE
+ | protocols::GB_SVC_TYPE_INTF_REFCLK_DISABLE,
+ ) => self.intf_refclk_enable_disable(hd, hdr),
+ (
+ false,
+ protocols::GB_SVC_TYPE_INTF_UNIPRO_ENABLE
+ | protocols::GB_SVC_TYPE_INTF_UNIPRO_DISABLE,
+ ) => self.intf_unipro_enable_disable(hd, hdr),
+ (false, protocols::GB_SVC_TYPE_INTF_ACTIVATE) => self.intf_activate(hd, msg),
+ (false, protocols::GB_SVC_TYPE_INTF_RESUME) => self.intf_resume(hd, msg),
+ (true, protocols::GB_SVC_TYPE_PROTOCOL_VERSION) => self.send_svc_hello(hd)?,
+ (
+ true,
+ protocols::GB_SVC_TYPE_MODULE_INSERTED
+ | protocols::GB_SVC_TYPE_SVC_HELLO
+ | protocols::GB_SVC_TYPE_MODULE_REMOVED,
+ ) => {}
+ _ => return Err(code::ENOTSUPP),
+ };
+
+ Ok(())
+ }
+}
+
+struct GbHdDriver(Arc<Svc>);
+
+#[vtable]
+impl hd::HdDriver for GbHdDriver {
+ fn message_send(
+ data: &Self,
+ dest_cport_id: u16,
+ msg: &greybus::Message,
+ gfp_mask: Flags,
+ ) -> Result {
+ if dest_cport_id == protocols::GB_SVC_CPORT_ID {
+ data.0.handler(msg)
+ } else {
+ let conn = msg.operation().connection();
+ let intf = data.0.intf_by_id(conn.interface().unwrap().id())?;
+
+ let mut buf = KVec::with_capacity(msg.header().size().into(), gfp_mask)?;
+
+ buf.extend_from_slice(msg.header().as_bytes(), gfp_mask)?;
+ buf.extend_from_slice(msg.payload_bytes(), gfp_mask)?;
+
+ intf.write(&buf, conn.intf_cport_id(), gfp_mask)
+ }
+ }
+
+ fn message_cancel(_msg: &greybus::Message) {}
+}
+
+struct GreybusSoftSvc {
+ _hd: hd::Registration<GbHdDriver>,
+ _faux: faux::Registration,
+}
+
+impl kernel::Module for GreybusSoftSvc {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ pr_info!("gb_softsvc (init)\n");
+
+ // SAFETY: This runs once at module init, before anything else can reach `GLOBAL_STATE`.
+ unsafe { GLOBAL_STATE.init() };
+
+ let faux = faux::Registration::new(c_str!("gb-softsvc"), None)?;
+ let svc = Arc::pin_init(Svc::new(), GFP_KERNEL)?;
+ let data = GbHdDriver(svc.clone());
+
+ let dev = faux.as_ref().as_ref();
+ let hd = hd::Registration::new(dev, GB_OPERATION_SIZE_MAX, CPORT_ID_MAX + 1, Ok(data))?;
+
+ svc.send_version(hd.as_ref())?;
+
+ let global_state = GlobalState::new(svc.clone(), hd.as_ref().into());
+ let _ = GLOBAL_STATE.lock().replace(global_state);
+
+ Ok(GreybusSoftSvc {
+ _hd: hd,
+ _faux: faux,
+ })
+ }
+}
+
+impl Drop for GreybusSoftSvc {
+ fn drop(&mut self) {
+ let _ = GLOBAL_STATE.lock().take();
+ pr_info!("gb_softsvc (exit)\n");
+ }
+}
+
+/// The operations a Greybus interface provides.
+///
+/// A node is whatever sits behind an interface — a real transport, an in-kernel emulation, or
+/// anything else that can accept Greybus traffic. This module does not care which.
+pub trait InterfaceOps: Send + Sync {
+ /// Delivers `data` to the node's `cport`.
+ fn write(&self, data: &[u8], cport: u16, gfp_mask: Flags) -> Result<()>;
+}
+
+/// A module attached to the Greybus network.
+pub struct Module {
+ id: u8,
+ intf_count: u8,
+ gb_hd: ARef<hd::Device>,
+ svc: Arc<Svc>,
+}
+
+impl Module {
+ /// Attaches `node` as a new module and returns the interface id assigned to it.
+ pub fn new(intfs: &[Arc<dyn InterfaceOps>]) -> Result<Self> {
+ let Ok(intf_count) = u8::try_from(intfs.len()) else {
+ return Err(code::E2BIG);
+ };
+
+ if intf_count == 0 {
+ return Err(code::EINVAL);
+ }
+
+ let guard = GLOBAL_STATE.lock();
+ let state = guard.as_ref().ok_or(code::EAGAIN)?;
+ let id = state.svc.module_insert(&state.gb_hd, intfs)?;
+
+ Ok(Self {
+ id,
+ intf_count,
+ gb_hd: state.gb_hd.clone(),
+ svc: state.svc.clone(),
+ })
+ }
+
+ /// Delivers `msg`, received from interface `id` on its `cport`, to the Greybus core.
+ ///
+ /// `intf_id_rel` is the id relative to the actual interface. Eq, if an Module has 3 interfaces
+ /// then the relative id will be 0 or 1 or 2
+ /// `cport` is the interface-side CPort id.
+ ///
+ /// Fails with `EINVAL` if no connection is bound to that pair, or `EAGAIN` if the
+ /// host device has not been brought up yet.
+ pub fn submit_message(&self, intf_id_rel: u8, cport: u16, msg: &[u8]) -> Result<()> {
+ if intf_id_rel >= self.intf_count {
+ return Err(code::EINVAL);
+ }
+
+ let intf = self
+ .gb_hd
+ .find_connection_by_intf(self.id + intf_id_rel, cport)
+ .ok_or(code::EINVAL)?;
+ self.gb_hd.data_rcvd(intf.hd_cport_id(), msg);
+
+ Ok(())
+ }
+}
+
+impl Drop for Module {
+ fn drop(&mut self) {
+ let _ = self
+ .svc
+ .module_remove(&self.gb_hd, self.id, self.intf_count);
+ }
+}
diff --git a/drivers/greybus/gb_softsvc_exports.c b/drivers/greybus/gb_softsvc_exports.c
new file mode 100644
index 000000000000..a0e360c4d137
--- /dev/null
+++ b/drivers/greybus/gb_softsvc_exports.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 BeagleBoard.org. All rights reserved.
+
+/*
+ * Exports Rust symbols from the `gb_softsvc` crate for use by dependent modules.
+ *
+ * This is a workaround until the build system supports Rust cross-module
+ * dependencies natively.
+ */
+
+#include <linux/export.h>
+
+#define EXPORT_SYMBOL_RUST_GPL(sym) extern int sym; EXPORT_SYMBOL_GPL(sym)
+
+#include "exports_gb_softsvc_generated.h"
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
` (4 preceding siblings ...)
2026-08-27 7:54 ` [PATCH v2 5/7] drivers: greybus: Add software SVC implementation Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 8:02 ` sashiko-bot
2026-08-27 16:06 ` Conor Dooley
2026-08-27 7:54 ` [PATCH v2 7/7] greybus: Add Rust UART node driver Ayush Singh
6 siblings, 2 replies; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
Add devicetree bindings for the BeagleBoard.org BeagleConnect Freedom,
a wireless-enabled board based on the TI CC1352P7. The board exposes an
MCU that can be attached to a host over USB, UART, SPI or I2C, and is
described as a child node of the corresponding bus.
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
.../beagle/beagle,beagleconnect-freedom.yaml | 39 ++++++++++++++++++++++
MAINTAINERS | 6 ++++
2 files changed, 45 insertions(+)
diff --git a/Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml b/Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml
new file mode 100644
index 000000000000..be051a39acdc
--- /dev/null
+++ b/Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/beagle/beagle,beagleconnect-freedom.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: BeagleBoard.org BeagleConnect Freedom
+
+description:
+ The BeagleBoard.org BeagleConnect Freedom board connected over USB, UART, SPI or I2C.
+
+maintainers:
+ - Ayush Singh <ayush@beagleboard.org>
+
+properties:
+ compatible:
+ const: beagle,beagleconnect-freedom
+
+ reg:
+ maxItems: 1
+
+ vbat-supply: true
+
+required:
+ - compatible
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ serial {
+ mcu {
+ compatible = "beagle,beagleconnect-freedom";
+ vbat-supply = <&vbat>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 3ba56cdbf056..d047090be5f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11334,6 +11334,12 @@ L: greybus-dev@lists.linaro.org (moderated for non-subscribers)
S: Maintained
F: drivers/greybus/gb_softsvc.rs
+GREYBUS UART NODE DRIVERS
+M: Ayush Singh <ayush@beagleboard.com>
+L: greybus-dev@lists.linaro.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml
+
GREYBUS SUBSYSTEM
M: Johan Hovold <johan@kernel.org>
M: Alex Elder <elder@kernel.org>
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 7/7] greybus: Add Rust UART node driver
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
` (5 preceding siblings ...)
2026-08-27 7:54 ` [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom Ayush Singh
@ 2026-08-27 7:54 ` Ayush Singh
2026-08-27 8:13 ` sashiko-bot
6 siblings, 1 reply; 14+ messages in thread
From: Ayush Singh @ 2026-08-27 7:54 UTC (permalink / raw)
To: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki
Cc: greybus-dev, linux-kernel, rust-for-linux, linux-crypto,
devicetree, Ayush Singh
Add a driver for Greybus nodes attached over a plain serial port. The
node is registered with the software SVC (gb-softsvc), which handles the
SVC protocol on behalf of the AP, so no dedicated coprocessor running
SVC firmware is needed.
Greybus messages are carried over HDLC framing on the wire. Each frame
carries a one-byte address (0x01 for Greybus) and control byte, followed
by the 16-bit CPort ID and the Greybus message itself.
Port parameters are taken from the firmware node: "baudrate" if
present, otherwise 115200, with flow control and parity disabled.
Since gb-uart-node imports types from gb-softsvc, Rust to Rust calling
setup from nova-core [0] is being used.
[0]: https://lore.kernel.org/all/20260622-nova-exports-v5-0-6191773fc977@nvidia.com/
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
---
MAINTAINERS | 1 +
drivers/greybus/.gitignore | 1 +
drivers/greybus/Kconfig | 15 +++
drivers/greybus/Makefile | 48 ++++++++
drivers/greybus/gb_uart_node.rs | 245 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 310 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d047090be5f4..49c6dac72748 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11339,6 +11339,7 @@ M: Ayush Singh <ayush@beagleboard.com>
L: greybus-dev@lists.linaro.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml
+F: drivers/greybus/gb_uart_node.rs
GREYBUS SUBSYSTEM
M: Johan Hovold <johan@kernel.org>
diff --git a/drivers/greybus/.gitignore b/drivers/greybus/.gitignore
new file mode 100644
index 000000000000..ff9c4a3539b4
--- /dev/null
+++ b/drivers/greybus/.gitignore
@@ -0,0 +1 @@
+exports_gb_softsvc_generated.h
diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
index 381d1a6ee135..34de913af287 100644
--- a/drivers/greybus/Kconfig
+++ b/drivers/greybus/Kconfig
@@ -60,5 +60,20 @@ config GREYBUS_SOFTSVC
To compile this code as a module, choose M here: the module
will be called gb-softsvc.ko
+config GREYBUS_UART_NODE
+ tristate "Greybus UART node transport"
+ depends on RUST
+ depends on GREYBUS_SOFTSVC
+ depends on RUST_SERIAL_DEV_BUS_ABSTRACTIONS
+ select RUST_CRC_CCITT_ABSTRACTIONS
+ help
+ Select this option if you have a Greybus node connected over a
+ serial port. The node is registered with the software SVC, which
+ handles the SVC protocol on behalf of the AP, so no dedicated
+ coprocessor running SVC firmware is required.
+
+ To compile this code as a module, choose M here: the module
+ will be called gb-uart-node.ko
+
endif # GREYBUS
diff --git a/drivers/greybus/Makefile b/drivers/greybus/Makefile
index e6f594128802..81151963c01e 100644
--- a/drivers/greybus/Makefile
+++ b/drivers/greybus/Makefile
@@ -28,3 +28,51 @@ obj-$(CONFIG_GREYBUS_ES2) += gb-es2.o
obj-$(CONFIG_GREYBUS_SOFTSVC) += gb-softsvc.o
gb-softsvc-y += gb_softsvc.o gb_softsvc_exports.o
+obj-$(CONFIG_GREYBUS_UART_NODE) += gb-uart-node.o
+gb-uart-node-y += gb_uart_node.o
+
+# Export Rust symbols from gb-softsvc only if gb-uart-node actually references them.
+gb-softsvc-export-deps := $(if $(CONFIG_GREYBUS_UART_NODE),$(obj)/gb_uart_node.o)
+
+rust_needed_exports = \
+ { $(if $(strip $(2)),$(NM) -u $(2);,) echo "__DEFINED_RUST_SYMBOLS__"; \
+ $(NM) -p --defined-only $(1); } | \
+ awk -v fmt='$(3)' ' \
+ /^__DEFINED_RUST_SYMBOLS__$$/ { defs = 1; next } \
+ !defs { if ($$NF ~ /^_R/) needed[$$NF] = 1; next } \
+ defs && $$2 ~ /(T|R|D|B)/ && $$3 ~ /^_R/ && \
+ $$3 !~ /_(init|cleanup)_module$$/ && \
+ $$3 !~ /__(pfx|cfi|odr_asan)/ && \
+ $$3 in needed { printf fmt, $$3 } \
+ '
+
+quiet_cmd_exports = EXPORTS $@
+ cmd_exports = \
+ $(call rust_needed_exports,$<,$(gb-softsvc-export-deps),EXPORT_SYMBOL_RUST_GPL(%s);\n) > $@
+
+$(obj)/exports_gb_softsvc_generated.h: $(obj)/gb_softsvc.o $(gb-softsvc-export-deps) FORCE
+ $(call if_changed,exports)
+
+targets += exports_gb_softsvc_generated.h
+
+$(obj)/gb_softsvc_exports.o: $(obj)/exports_gb_softsvc_generated.h
+CFLAGS_gb_softsvc_exports.o := -I $(objtree)/$(obj)
+
+ifdef CONFIG_MODVERSIONS
+# The C export shim declares Rust symbols as `extern int`, so reuse its export
+# list but generate symbol CRCs from the Rust object instead of the shim's DWARF.
+$(obj)/gb_softsvc_exports.o: private cmd_gensymtypes_c = \
+ $(call getexportsymbols,\1) | \
+ $(objtree)/scripts/gendwarfksyms/gendwarfksyms \
+ $(if $(KBUILD_GENDWARFKSYMS_STABLE), --stable) \
+ $(if $(KBUILD_SYMTYPES), --symtypes $(@:.o=.symtypes),) \
+ $(obj)/gb_softsvc.o
+endif
+
+# Output nova-core's crate metadata for use by nova-drm at compile time.
+RUSTFLAGS_gb_softsvc.o += \
+ --emit=metadata=$(objtree)/$(obj)/libgb_softsvc.rmeta
+
+# Allow nova-drm to import nova-core's types.
+$(obj)/gb_uart_node.o: $(obj)/gb_softsvc.o
+RUSTFLAGS_gb_uart_node.o := -L $(objtree)/$(obj) --extern gb_softsvc
diff --git a/drivers/greybus/gb_uart_node.rs b/drivers/greybus/gb_uart_node.rs
new file mode 100644
index 000000000000..3eb4f8ab3655
--- /dev/null
+++ b/drivers/greybus/gb_uart_node.rs
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Greybus UART Node driver
+
+use kernel::{
+ alloc::Flags,
+ crc_ccitt::crc_ccitt,
+ device::{
+ AsBusDevice,
+ Bound,
+ Core, //
+ },
+ error::code,
+ new_spinlock, of,
+ prelude::*,
+ serdev,
+ sync::{
+ aref::ARef,
+ Arc,
+ SpinLock, //
+ },
+};
+
+use zerocopy::little_endian;
+use zerocopy_derive::{FromBytes, Immutable, KnownLayout};
+
+const HDLC_MAX_FRAME_LEN: usize = 256;
+
+const HDLC_FRAME: u8 = 0x7E;
+const HDLC_ESC: u8 = 0x7D;
+const HDLC_XOR: u8 = 0x20;
+const HDLC_EXPECTED_CRC: u16 = 0xf0b8;
+
+const ADDRESS_GREYBUS: u8 = 0x01;
+
+#[repr(C, packed)]
+#[derive(FromBytes, Immutable, KnownLayout)]
+struct GreybusFrame {
+ cport: little_endian::U16,
+ msg: [u8],
+}
+
+struct HdlcRx {
+ rx_buf: KVec<u8>,
+ rx_in_esc: bool,
+ sdev: ARef<serdev::Device>,
+ node: gb_softsvc::Module,
+}
+
+impl HdlcRx {
+ fn new(sdev: ARef<serdev::Device>, node: gb_softsvc::Module) -> Result<Self> {
+ Ok(Self {
+ node,
+ sdev,
+ rx_buf: KVec::with_capacity(HDLC_MAX_FRAME_LEN, GFP_KERNEL)?,
+ rx_in_esc: false,
+ })
+ }
+
+ fn frame_finish(&self) -> Result<()> {
+ if self.rx_buf.len() < 4 {
+ return Err(code::EFAULT);
+ }
+
+ let crc = crc_ccitt(0xffff, &self.rx_buf);
+ if crc != HDLC_EXPECTED_CRC {
+ dev_warn!(self.sdev.as_ref(), "CRC failed {}", crc);
+ return Ok(());
+ }
+
+ let addr = self.rx_buf[0];
+ let _ctrl = self.rx_buf[1];
+ let payload = &self.rx_buf[2..self.rx_buf.len() - size_of::<u16>()];
+
+ match addr {
+ ADDRESS_GREYBUS => {
+ let frame = GreybusFrame::ref_from_bytes(payload).map_err(|_| code::EINVAL)?;
+ self.node.submit_message(0, frame.cport.into(), &frame.msg)
+ }
+ _ => Err(code::EINVAL),
+ }
+ }
+
+ fn rx(&mut self, data: &[u8]) -> usize {
+ for i in data.iter() {
+ match *i {
+ HDLC_FRAME => {
+ if !self.rx_buf.is_empty() {
+ if let Err(e) = self.frame_finish() {
+ dev_warn!(self.sdev.as_ref(), "bad frame: {e:?}\n");
+ }
+ }
+
+ self.rx_buf.clear();
+ self.rx_in_esc = false;
+ }
+ HDLC_ESC => self.rx_in_esc = true,
+ _ => {
+ let c = if self.rx_in_esc { *i ^ HDLC_XOR } else { *i };
+ self.rx_in_esc = false;
+
+ if self.rx_buf.push_within_capacity(c).is_err() {
+ dev_warn!(self.sdev.as_ref(), "buffer overflow. Dropping frame");
+
+ self.rx_buf.clear();
+ self.rx_in_esc = false;
+ }
+ }
+ }
+ }
+
+ data.len()
+ }
+}
+
+struct GbNode {
+ sdev: ARef<serdev::Device>,
+}
+
+impl GbNode {
+ const fn new(sdev: ARef<serdev::Device>) -> Self {
+ Self { sdev }
+ }
+
+ fn fill_buf(mut crc: u16, data: &[u8], buf: &mut KVec<u8>) -> Result<u16> {
+ for i in data {
+ crc = crc_ccitt(crc, &[*i]);
+ if *i == HDLC_ESC || *i == HDLC_FRAME {
+ buf.push_within_capacity(HDLC_ESC)?;
+ buf.push_within_capacity(i ^ HDLC_XOR)?;
+ } else {
+ buf.push_within_capacity(*i)?;
+ }
+ }
+
+ Ok(crc)
+ }
+}
+
+impl gb_softsvc::InterfaceOps for GbNode {
+ fn write(&self, data: &[u8], cport: u16, gfp_mask: Flags) -> Result<()> {
+ // SAFETY: `GbNode` only exists while its serdev driver is bound, so the device is in the
+ // `Bound` state for the duration of this call.
+ let bound: &serdev::Device<Bound> =
+ unsafe { serdev::Device::from_device(self.sdev.as_ref().as_bound()) };
+
+ let mut buf = KVec::with_capacity(HDLC_MAX_FRAME_LEN, gfp_mask)?;
+
+ let mut crc = 0xffff;
+
+ buf.push_within_capacity(HDLC_FRAME)?;
+
+ crc = Self::fill_buf(crc, &[ADDRESS_GREYBUS, 0x03], &mut buf)?;
+ crc = Self::fill_buf(crc, &cport.to_le_bytes(), &mut buf)?;
+ crc = Self::fill_buf(crc, data, &mut buf)?;
+
+ crc ^= 0xffff;
+ Self::fill_buf(crc, &crc.to_le_bytes(), &mut buf)?;
+
+ buf.push_within_capacity(HDLC_FRAME)?;
+
+ bound.write_all(&buf, 0)?;
+
+ Ok(())
+ }
+}
+
+#[pin_data]
+struct GbUartNode {
+ #[pin]
+ rx: SpinLock<Option<HdlcRx>>,
+}
+
+impl GbUartNode {
+ fn init(sdev: &serdev::Device<Core<'_>>) -> Result<HdlcRx> {
+ if sdev
+ .set_baudrate(
+ sdev.as_ref()
+ .fwnode()
+ .and_then(|fwnode| fwnode.property_read(c"baudrate").optional())
+ .unwrap_or(115200),
+ )
+ .is_err()
+ {
+ return Err(EINVAL);
+ }
+ sdev.set_flow_control(false);
+ sdev.set_parity(serdev::Parity::None)?;
+
+ let node = gb_softsvc::Module::new(&[Arc::new(GbNode::new(sdev.into()), GFP_KERNEL)?])?;
+
+ HdlcRx::new(sdev.into(), node)
+ }
+}
+
+kernel::of_device_table!(
+ OF_TABLE,
+ <GbUartNode as serdev::Driver>::IdInfo,
+ [(of::DeviceId::new(c"beagle,beagleconnect-freedom"), ())]
+);
+
+#[vtable]
+impl serdev::Driver for GbUartNode {
+ type IdInfo = ();
+ type Data<'bound> = Self;
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe<'bound>(
+ sdev: &'bound serdev::Device<Core<'_>>,
+ _info: Option<&'bound Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> + 'bound {
+ dev_dbg!(sdev.as_ref(), "Probe gb_uart_node.\n");
+
+ try_pin_init!(Self {
+ rx <- new_spinlock!(Some(Self::init(sdev)?), "gb_uart_node::rx"),
+ }? Error)
+ }
+
+ fn receive<'bound>(
+ _sdev: &'bound serdev::Device<Bound>,
+ this: Pin<&Self>,
+ data: &[u8],
+ ) -> usize {
+ if let Some(mut guard) = this.rx.try_lock() {
+ if let Some(ref mut hdlc_rx) = *guard {
+ return hdlc_rx.rx(data);
+ }
+ }
+
+ 0
+ }
+
+ fn unbind<'bound>(_: &'bound serdev::Device<Core<'_>>, this: Pin<&Self::Data<'bound>>) {
+ // Getting a bound device is not possible after this point. So drop HdlcRx.
+ let _ = this.rx.lock().take();
+ }
+}
+
+kernel::module_serdev_device_driver! {
+ type: GbUartNode,
+ name: "gb_uart_node",
+ authors: ["Ayush Singh <ayush@beagleboard.org>"],
+ description: "Greybus node connected over UART",
+ license: "GPL v2",
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom
2026-08-27 7:54 ` [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom Ayush Singh
@ 2026-08-27 8:02 ` sashiko-bot
2026-08-27 16:06 ` Conor Dooley
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-27 8:02 UTC (permalink / raw)
To: Ayush Singh; +Cc: ojeda, conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The binding incorrectly enforces SPI constraints globally and rejects standard properties for non-SPI buses.
- [Medium] Typographical error in the maintainer email domain causes misdirection of patches.
--
commit c9a38c235f36e95264430893cbbb7407d8fc8ad8
Author: Ayush Singh <ayush@beagleboard.org>
dt-bindings: beagle: Add BeagleConnect Freedom
This commit adds devicetree bindings for the BeagleBoard.org BeagleConnect
Freedom board based on the TI CC1352P7. The board exposes an MCU that can
be attached to a host over USB, UART, SPI, or I2C. It also adds a new
MAINTAINERS entry for the Greybus UART node drivers.
> diff --git a/Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml b/Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml
> new file mode 100644
> index 0000000000000..be051a39acdc6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/beagle/beagle,beagleconnect-freedom.yaml
[ ... ]
> +required:
> + - compatible
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
[Severity: Medium]
The commit message mentions this MCU can be attached to a host over USB,
UART, SPI, or I2C. Does this unconditional reference to the SPI peripheral
properties combined with unevaluatedProperties: false prevent standard bus
properties from passing dt_binding_check?
For example, if the device is configured on a UART bus with a standard
property like current-speed = <115200>;, the toolchain will reject it because
it is not in the local schema or the referenced SPI schema. Additionally,
does this inadvertently enforce SPI constraints, such as limiting reg values
to a maximum of 256, on I2C addresses?
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 61f3fd5c509ca..a9888ace2f916 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
[ ... ]
> @@ -11271,6 +11271,12 @@ L: greybus-dev@lists.linaro.org (moderated for non-subscribers)
> S: Maintained
> F: drivers/greybus/gb_softsvc.rs
>
> +GREYBUS UART NODE DRIVERS
> +M: Ayush Singh <ayush@beagleboard.com>
[Severity: Medium]
This isn't a runtime bug, but is the email address domain in the MAINTAINERS
entry correct?
The patch authorship and the dt-binding maintainer field both use
@beagleboard.org, but this entry uses @beagleboard.com. Will this cause
scripts/get_maintainer.pl to return an incorrect email address and misdirect
future patches?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-gb-uart-transport-v2-0-a03bb1f5fbd1@beagleboard.org?part=6
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 5/7] drivers: greybus: Add software SVC implementation
2026-08-27 7:54 ` [PATCH v2 5/7] drivers: greybus: Add software SVC implementation Ayush Singh
@ 2026-08-27 8:09 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-27 8:09 UTC (permalink / raw)
To: Ayush Singh; +Cc: conor+dt, robh, ojeda, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] `Msg<P>` unsoundly implements `AsBytes` for an unconstrained `P`, allowing types with pointers, padding, or interior mutability to be read as bytes.
- [High] Kconfig `GREYBUS_SOFTSVC` relies on the `kernel::greybus` Rust module but lacks a dependency on `RUST_GREYBUS_ABSTRACTIONS`.
- [High] The `message_send` function calls `intf_by_id`, which acquires a `Mutex` (`self.intf_map.lock()`), but `message_send` can be called from atomic contexts.
- [Medium] Integer underflow panic in `Svc::intf_by_id` if `id` is 0.
--
commit 7f12c32b073d73645edd3aaf941609c3637db70d
Author: Ayush Singh <ayush@beagleboard.org>
drivers: greybus: Add software SVC implementation
This commit adds gb-softsvc, a Rust implementation of the Greybus SVC
(Supervisory Controller) that lives in the kernel. It provides an SVC
emulation for host drivers on transports without a native SVC.
Link: https://lore.kernel.org/all/20260622-nova-exports-v5-0-6191773fc977@nvidia.com/
> diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
> index 3bf3b86914423..381d1a6ee135c 100644
> --- a/drivers/greybus/Kconfig
> +++ b/drivers/greybus/Kconfig
[ ... ]
> @@ -48,5 +48,17 @@ config GREYBUS_ES2
> To compile this code as a module, choose M here: the module
> will be called gb-es2.ko
>
> +config GREYBUS_SOFTSVC
> + tristate "Greybus software SVC"
> + depends on RUST
[Severity: High]
Will this fail to build if RUST_GREYBUS_ABSTRACTIONS is disabled?
Since this driver relies on the kernel::greybus Rust module, it seems it
needs a dependency on RUST_GREYBUS_ABSTRACTIONS to ensure the abstractions
are compiled in.
> + help
> + Select this option to enable a software implementation of the
> + Greybus SVC (Supervisory Controller) running in the kernel. It can
[ ... ]
> diff --git a/drivers/greybus/gb_softsvc.rs b/drivers/greybus/gb_softsvc.rs
> new file mode 100644
> index 0000000000000..79e2752e42ba0
> --- /dev/null
> +++ b/drivers/greybus/gb_softsvc.rs
[ ... ]
> +#[repr(C, packed)]
> +struct Msg<P> {
> + hdr: protocols::GbOperationMsgHdr,
> + payload: P,
> +}
> +
> +// SAFETY: `Msg` is a POD type with no padding and no interior mutability.
> +unsafe impl<P> kernel::transmute::AsBytes for Msg<P> {}
[Severity: High]
Does this implementation allow unsound transmutations for unconstrained types?
Because P is generic and lacks bounds, any type could be instantiated here,
including types with pointers, padding, or interior mutability. Should this
implementation require P: kernel::transmute::AsBytes to ensure the type
is actually safe to read as bytes?
[ ... ]
> +#[pin_data]
> +struct Svc {
> + #[pin]
> + svc_operation_id: SpinLock<IdPool>,
> + // Pos 0 = INTF_ID_START
> + #[pin]
> + intf_map: Mutex<[Option<Arc<dyn InterfaceOps>>; INTF_MAP_LEN]>,
> +}
[ ... ]
> + fn intf_by_id(&self, id: u8) -> Result<Arc<dyn InterfaceOps>> {
> + let id = id - INTF_ID_START;
[Severity: Medium]
Can this subtraction underflow and cause a kernel panic?
If a malformed message or core bug routes a message to the AP interface
(where id is 0), id - INTF_ID_START will underflow since INTF_ID_START
is 1.
> + let guard = self.intf_map.lock();
> +
> + guard
> + .get(usize::from(id))
> + .ok_or(code::ENODEV)?
> + .as_ref()
> + .ok_or(code::ENODEV)
> + .cloned()
> + }
[ ... ]
> +#[vtable]
> +impl hd::HdDriver for GbHdDriver {
> + fn message_send(
> + data: &Self,
> + dest_cport_id: u16,
> + msg: &greybus::Message,
> + gfp_mask: Flags,
> + ) -> Result {
> + if dest_cport_id == protocols::GB_SVC_CPORT_ID {
> + data.0.handler(msg)
> + } else {
> + let conn = msg.operation().connection();
> + let intf = data.0.intf_by_id(conn.interface().unwrap().id())?;
[Severity: High]
Can this lead to a scheduling-while-atomic oops?
The Greybus core can call message_send() from atomic contexts (which is why
it passes a gfp_mask). However, intf_by_id() acquires intf_map.lock(), which
is a sleepable Mutex. Should intf_map use a SpinLock instead to prevent
sleeping in atomic context?
> +
> + let mut buf = KVec::with_capacity(msg.header().size().into(), gfp_mask)?;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-gb-uart-transport-v2-0-a03bb1f5fbd1@beagleboard.org?part=5
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 4/7] rust: kernel: Add greybus abstractions
2026-08-27 7:54 ` [PATCH v2 4/7] rust: kernel: Add greybus abstractions Ayush Singh
@ 2026-08-27 8:12 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-27 8:12 UTC (permalink / raw)
To: Ayush Singh; +Cc: ojeda, conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Memory leak of `gb_host_device` and driver data on error paths in `Registration::new`
- [High] Use-After-Free in safe `Connection::host_device()` and `Connection::interface()` methods
- [High] Race condition causing Use-After-Free of driver data during `message_send`
- [Medium] Kconfig selects GREYBUS without inheriting SYSFS dependency
- [Medium] Incomplete abstraction: `HdDriver::message_cancel` lacks access to driver state
- [Low] API Documentation erroneously instructs users to call a non-existent method
- [Low] Erroneous safety justification masks protocol struct misunderstanding
--
commit aca3dd0934984891ba1944b198bd9679ec786c2e
Author: Ayush Singh <ayush@beagleboard.org>
rust: kernel: Add greybus abstractions
This commit adds Rust abstractions for the Greybus core, providing the
basic structures, host device abstractions, and protocol types needed to
implement a Greybus host driver in Rust.
> diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
> --- a/drivers/greybus/Kconfig
> +++ b/drivers/greybus/Kconfig
[ ... ]
> +config RUST_GREYBUS_ABSTRACTIONS
> + bool "Rust greybus abstractions"
> + depends on RUST
> + select GREYBUS
> + help
> + This enables the Rust abstraction for the Greybus API.
[Severity: Medium]
Does this Kconfig entry miss a dependency on SYSFS?
The menuconfig GREYBUS depends on SYSFS. By using select GREYBUS here
without inheriting the depends on SYSFS clause, could this violate Kconfig
dependency rules and cause unmet direct dependency warnings or build
failures on minimal configurations where SYSFS is disabled?
> diff --git a/rust/kernel/greybus/hd.rs b/rust/kernel/greybus/hd.rs
> --- /dev/null
> +++ b/rust/kernel/greybus/hd.rs
[ ... ]
> + /// Transmits `msg` to `dest_cport_id`.
> + ///
> + /// This may be called in atomic context and therefore must not sleep; queue the message and
> + /// return. Once the core is allowed to release the message, call
> + /// [`HostDevice::message_sent`].
> + fn message_send(data: &Self, dest_cport_id: u16, msg: &Message, gfp_mask: Flags) -> Result;
[Severity: Low]
Is the documentation instructing users to call a non-existent method?
The comment mentions calling HostDevice::message_sent, but this method
does not seem to exist. The framework handles calling sent(0) automatically
when Ok(()) is returned. Should the documentation be updated to reflect the
actual API contract?
[ ... ]
> + /// Aborts the transmission of a message previously handed to [`HdDriver::message_send`].
> + ///
> + /// Always called in process context.
> + fn message_cancel(msg: &Message);
[Severity: Medium]
Is there a way for a driver to safely access its private state when
cancelling a message?
To cancel a message, a driver typically must remove it from internal
hardware queues, which requires access to its private state. Since
message_cancel omits the &Self parameter (unlike message_send), drivers
might be forced to write unsafe code to chase pointers through the message's
connection to extract their data. Could we add &Self to the trait method?
[ ... ]
> + pub fn new(
> + parent: &device::Device,
> + buffer_size_max: usize,
> + num_cports: usize,
> + data: impl PinInit<T, Error>,
> + ) -> Result<Self> {
> + // SAFETY: `parent` is a valid device, and the driver table is `'static`. The core only
> + // ever reads through the driver pointer, so casting away `const` is fine.
> + let hd = from_err_ptr(unsafe {
> + bindings::gb_hd_create(
[ ... ]
> + )
> + })?;
> +
> + // SAFETY: `hd` is a valid pointer to `gb_host_device`.
> + let hd_dev: &Device<device::CoreInternal<'_>> = unsafe { Device::from_raw(hd) };
> + hd_dev.as_ref().set_drvdata(data)?;
> +
> + // SAFETY: By the type invariant the host device is valid, and it has not been added yet.
> + to_result(unsafe { bindings::gb_hd_add(hd) })?;
[Severity: High]
Can this leak the gb_host_device and pinned driver data on error paths?
bindings::gb_hd_create() allocates the gb_host_device and sets its reference
count to 1. If either set_drvdata or bindings::gb_hd_add fails, the ? operator
will return early.
Since the Registration structure hasn't been instantiated yet, its Drop
implementation won't run, meaning there is no cleanup to call gb_hd_put(hd)
or free the pinned driver data, causing a permanent leak.
[ ... ]
> + fn drop<'a>(&'a mut self) {
> + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
> + // `struct gb_host_device`.
> + unsafe { bindings::gb_hd_del(self.as_raw()) }
> +
> + {
> + // SAFETY: `hd` is a valid pointer to `gb_host_device`.
> + let hd_dev = unsafe { Device::<device::CoreInternal<'_>>::from_raw(self.as_raw()) };
> + // SAFETY: The driver data was set to a `T` in `Registration::new()` and has not been
> + // taken since, and this is the only place that takes it.
> + drop(unsafe { hd_dev.as_ref().drvdata_obtain::<T>() });
> + }
[Severity: High]
Does this create a use-after-free race condition with preempted message
send threads?
gb_hd_del() is invoked when Registration drops, and it returns synchronously
without waiting for all in-flight gb_message_send operations to finish.
The driver data (T) is immediately extracted and dropped following
gb_hd_del().
If a thread executing gb_operation_request_send is preempted just before
calling gb_message_send, it could resume and invoke the Rust message_send
wrapper, which calls hd.as_ref().drvdata_borrow() and passes a reference
of the freed T to the driver's implementation.
> diff --git a/rust/kernel/greybus/mod.rs b/rust/kernel/greybus/mod.rs
> --- /dev/null
> +++ b/rust/kernel/greybus/mod.rs
[ ... ]
> + pub const fn interface(&self) -> Option<&Interface> {
> + // SAFETY: By the type invariant the connection is valid.
> + let intf_ptr = unsafe { (*self.0.get()).intf };
> +
> + if intf_ptr.is_null() {
> + None
> + } else {
> + // SAFETY: By the previous check, intf_ptr is valid.
> + Some(unsafe { Interface::from_raw(intf_ptr) })
> + }
> + }
[ ... ]
> + pub const fn host_device(&self) -> &hd::Device {
> + // SAFETY: By the type invariant the connection is valid, and its `hd` is alive for as
> + // long as the connection is.
> + unsafe { hd::Device::from_raw((*self.0.get()).hd) }
> + }
[Severity: High]
Can these methods lead to a use-after-free by returning a dangling pointer?
Safe Rust code can hold an ARef<Connection>, which increments the refcount
of the gb_connection. However, the Greybus core doesn't appear to take a
reference count on the gb_host_device or gb_interface stored in the
gb_connection structure.
When the device is disconnected, if the core frees the host device and
interface, won't this leave dangling pointers inside the still-alive
gb_connection? Calling Connection::interface() or Connection::host_device()
would then safely wrap and return these dangling raw pointers.
> diff --git a/rust/kernel/greybus/protocols.rs b/rust/kernel/greybus/protocols.rs
> --- /dev/null
> +++ b/rust/kernel/greybus/protocols.rs
[ ... ]
> +/// Request for [`GB_SVC_TYPE_INTF_SET_PWRM`].
> +#[repr(transparent)]
> +pub struct GbSvcIntfSetPwrmRequest(bindings::gb_svc_intf_set_pwrm_request);
> +
> +// SAFETY: `gb_svc_intf_set_pwrm_request` is a struct of `u8` fields, so every bit pattern of its
> +// size is a valid instance.
> +unsafe impl kernel::transmute::FromBytes for GbSvcIntfSetPwrmRequest {}
[Severity: Low]
Is the safety comment here factually accurate?
The comment claims the struct contains only u8 fields. However, the
underlying C structure actually contains __le32 quirks and two nested
struct gb_svc_l2_timer_cfg containing __le16 fields. While the FromBytes
implementation might still be safe due to the struct being packed and
composed of integers, could this justification mask a misunderstanding
of the hardware protocol layout?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-gb-uart-transport-v2-0-a03bb1f5fbd1@beagleboard.org?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 2/7] greybus: connection: Add gb_connection_hd_find_by_intf()
2026-08-27 7:54 ` [PATCH v2 2/7] greybus: connection: Add gb_connection_hd_find_by_intf() Ayush Singh
@ 2026-08-27 8:13 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-27 8:13 UTC (permalink / raw)
To: Ayush Singh; +Cc: robh, devicetree, ojeda, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Returning a refcounted gb_connection for external use enables Use-After-Free vulnerabilities because connection teardown ignores the refcount.
Pre-existing issues:
- [High] Race condition between `gb_operation_request_send()` and `gb_connection_destroy()` leads to a message being sent to a released or reassigned `hd_cport_id`.
--
commit 4b0f9446338941774da275dbe57306c7d950598d
Author: Ayush Singh <ayush@beagleboard.org>
greybus: connection: Add gb_connection_hd_find_by_intf()
This commit introduces gb_connection_hd_find_by_intf() to look up a
connection on a host device using its remote interface ID and CPort ID. It
returns a reference-counted connection pointer to the caller.
> diff --git a/drivers/greybus/connection.c b/drivers/greybus/connection.c
> index f8fda22bc4667..dc9eb49a69bcf 100644
> --- a/drivers/greybus/connection.c
> +++ b/drivers/greybus/connection.c
> @@ -71,6 +71,29 @@ gb_connection_hd_find(struct gb_host_device *hd, u16 cport_id)
> return connection;
> }
>
> +struct gb_connection *gb_connection_hd_find_by_intf(struct gb_host_device *hd,
> + u8 intf_id, u16 intf_cport)
> +{
> + struct gb_connection *connection;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&gb_connections_lock, flags);
> + list_for_each_entry(connection, &hd->connections, hd_links) {
> + if (connection->intf &&
> + connection->intf->interface_id == intf_id &&
> + connection->intf_cport_id == intf_cport) {
> + gb_connection_get(connection);
[Severity: High]
Does returning a refcounted gb_connection for external use enable
use-after-free vulnerabilities?
If an external driver like gb_softsvc uses this new API to obtain the
connection, a hot-unplug event could trigger gb_connection_destroy(). Since
connection teardown ignores this kref, it will unconditionally destroy the
workqueue and release the CPort.
Can the external driver subsequently access the torn-down connection,
resulting in a use-after-free of connection->intf and resource confusion
of connection->wq and hd_cport_id?
> + goto found;
> + }
> + }
> + connection = NULL;
> +found:
> + spin_unlock_irqrestore(&gb_connections_lock, flags);
> +
> + return connection;
> +}
> +EXPORT_SYMBOL_GPL(gb_connection_hd_find_by_intf);
[ ... ]
Looking at gb_operation_request_send() in drivers/greybus/operation.c:
> /*
> * Get an extra reference on the operation. It'll be dropped when the
> * operation completes.
> */
> gb_operation_get(operation);
> ret = gb_operation_get_active(operation);
> if (ret)
> goto err_put;
>
> ret = gb_message_send(operation->request, gfp);
[Severity: High]
This is a pre-existing issue, but can a race condition occur between
gb_operation_request_send() and gb_connection_destroy() leading to a message
being sent to a released or reassigned hd_cport_id?
If gb_operation_request_send() gets the active reference, unlocks the
connection, and is preempted before calling gb_message_send():
gb_operation_get_active(operation);
// preempted here
Concurrent gb_connection_destroy() could cancel the operation, wait for the
completion work to drop the active ref, and then destroy the connection
resources (setting hd_cport_id to CPORT_ID_BAD and releasing it).
When the preempted thread resumes, could it use the released or recycled
hd_cport_id when calling gb_message_send(), potentially causing the host
driver to send data to the wrong endpoint or panic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-gb-uart-transport-v2-0-a03bb1f5fbd1@beagleboard.org?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 7/7] greybus: Add Rust UART node driver
2026-08-27 7:54 ` [PATCH v2 7/7] greybus: Add Rust UART node driver Ayush Singh
@ 2026-08-27 8:13 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-27 8:13 UTC (permalink / raw)
To: Ayush Singh; +Cc: conor+dt, robh, devicetree, ojeda
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] Strict 256-byte capacity limitation in HDLC buffers will drop valid Greybus messages.
- [High] Incorrect RX buffer overflow handling treats the tail of oversized frames as new messages.
- [High] Dropping `gb_softsvc::Module` while holding a spinlock causes sleeping in atomic context (deadlock/panic).
- [High] `GbNode::write` unsafely assumes the serdev device is bound and races with driver `unbind`, leading to Use-After-Free.
- [High] Concurrent calls to `GbNode::write` lack serialization, corrupting the HDLC protocol stream.
- [Medium] Unbounded log spam in the UART RX path when receiving overlong frames or invalid CRCs.
- [Low] The config `GREYBUS_UART_NODE` depends on a non-existent config symbol, making it impossible to enable the driver.
- [Low] Import formatting violates the vertical import style guideline.
- [Low] Makefile comments contain copy-paste errors referencing the wrong subsystem (`nova-core`).
--
commit 3bb7b1d9bc5abe108b82d7471de8f5bbfb1088d6
Author: Ayush Singh <ayush@beagleboard.org>
greybus: Add Rust UART node driver
Add a driver for Greybus nodes attached over a plain serial port. The
node is registered with the software SVC (gb-softsvc), which handles the
SVC protocol on behalf of the AP, so no dedicated coprocessor running
SVC firmware is needed.
Link: https://lore.kernel.org/all/20260622-nova-exports-v5-0-6191773fc977@nvidia.com/
> diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
> index 381d1a6ee135c..34de913af287e 100644
> --- a/drivers/greybus/Kconfig
> +++ b/drivers/greybus/Kconfig
> @@ -60,5 +60,20 @@ config GREYBUS_SOFTSVC
> To compile this code as a module, choose M here: the module
> will be called gb-softsvc.ko
>
> +config GREYBUS_UART_NODE
> + tristate "Greybus UART node transport"
> + depends on RUST
> + depends on GREYBUS_SOFTSVC
> + depends on RUST_SERIAL_DEV_BUS_ABSTRACTIONS
[Severity: Low]
Is RUST_SERIAL_DEV_BUS_ABSTRACTIONS defined anywhere in the kernel tree?
It appears this config symbol does not exist, which causes an unmet dependency
that will silently break the build configuration and prevent this driver from
being compiled.
[ ... ]
> diff --git a/drivers/greybus/Makefile b/drivers/greybus/Makefile
> index e6f5941288027..81151963c01ea 100644
> --- a/drivers/greybus/Makefile
> +++ b/drivers/greybus/Makefile
[ ... ]
> +# Output nova-core's crate metadata for use by nova-drm at compile time.
> +RUSTFLAGS_gb_softsvc.o += \
> + --emit=metadata=$(objtree)/$(obj)/libgb_softsvc.rmeta
> +
> +# Allow nova-drm to import nova-core's types.
[Severity: Low]
This isn't a bug, but should these comments be updated to reflect the Greybus
subsystem instead of nova-core and nova-drm?
> +$(obj)/gb_uart_node.o: $(obj)/gb_softsvc.o
> +RUSTFLAGS_gb_uart_node.o := -L $(objtree)/$(obj) --extern gb_softsvc
> diff --git a/drivers/greybus/gb_uart_node.rs b/drivers/greybus/gb_uart_node.rs
> new file mode 100644
> index 0000000000000..3eb4f8ab36554
> --- /dev/null
> +++ b/drivers/greybus/gb_uart_node.rs
> @@ -0,0 +1,245 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Greybus UART Node driver
> +
> +use kernel::{
> + alloc::Flags,
> + crc_ccitt::crc_ccitt,
> + device::{
> + AsBusDevice,
> + Bound,
> + Core, //
> + },
> + error::code,
> + new_spinlock, of,
[Severity: Low]
This isn't a bug, but does this formatting follow the kernel vertical import
style guideline? Putting new_spinlock and of on the same line violates the
one-item-per-line rule.
> + prelude::*,
> + serdev,
> + sync::{
> + aref::ARef,
> + Arc,
> + SpinLock, //
> + },
> +};
> +
> +use zerocopy::little_endian;
> +use zerocopy_derive::{FromBytes, Immutable, KnownLayout};
> +
> +const HDLC_MAX_FRAME_LEN: usize = 256;
[Severity: High]
Does a strict 256-byte capacity limitation in HDLC buffers drop valid Greybus
messages?
Greybus protocol supports dynamic payload sizes that can easily exceed 256
bytes. Furthermore, HDLC byte escaping (fill_buf) can double the size of the
payload on the wire.
Any message exceeding 256 bytes (after escaping) will cause
push_within_capacity() to fail, resulting in valid messages being dropped.
> +
> +const HDLC_FRAME: u8 = 0x7E;
[ ... ]
> +impl HdlcRx {
> + fn new(sdev: ARef<serdev::Device>, node: gb_softsvc::Module) -> Result<Self> {
> + Ok(Self {
> + node,
> + sdev,
> + rx_buf: KVec::with_capacity(HDLC_MAX_FRAME_LEN, GFP_KERNEL)?,
> + rx_in_esc: false,
> + })
> + }
> +
> + fn frame_finish(&self) -> Result<()> {
> + if self.rx_buf.len() < 4 {
> + return Err(code::EFAULT);
> + }
> +
> + let crc = crc_ccitt(0xffff, &self.rx_buf);
> + if crc != HDLC_EXPECTED_CRC {
> + dev_warn!(self.sdev.as_ref(), "CRC failed {}", crc);
[Severity: Medium]
Could this dev_warn!() be used to flood the kernel log?
If an attacker or a noisy line continuously sends invalid frames at high baud
rates, it would cause a Denial of Service. Should this be
dev_warn_ratelimited!()?
> + return Ok(());
> + }
[ ... ]
> + fn rx(&mut self, data: &[u8]) -> usize {
> + for i in data.iter() {
> + match *i {
> + HDLC_FRAME => {
> + if !self.rx_buf.is_empty() {
> + if let Err(e) = self.frame_finish() {
> + dev_warn!(self.sdev.as_ref(), "bad frame: {e:?}\n");
> + }
> + }
> +
> + self.rx_buf.clear();
> + self.rx_in_esc = false;
> + }
> + HDLC_ESC => self.rx_in_esc = true,
> + _ => {
> + let c = if self.rx_in_esc { *i ^ HDLC_XOR } else { *i };
> + self.rx_in_esc = false;
> +
> + if self.rx_buf.push_within_capacity(c).is_err() {
> + dev_warn!(self.sdev.as_ref(), "buffer overflow. Dropping frame");
> +
> + self.rx_buf.clear();
> + self.rx_in_esc = false;
[Severity: High]
Does clearing the buffer here correctly discard the rest of the overgrown frame?
The loop continues processing the remaining bytes of the exact same oversized
frame, erroneously pushing them into the newly empty buffer.
When the HDLC_FRAME delimiter arrives to mark the end of the oversized message,
frame_finish() is incorrectly called on this trailing garbage, which could lead
to corrupted data being processed if the CRC randomly matches.
Additionally, could this dev_warn!() cause an unbounded log spam?
dev_warn_ratelimited!() is generally preferred when reading from noisy lines.
> + }
> + }
> + }
> + }
> +
> + data.len()
> + }
> +}
[ ... ]
> +impl gb_softsvc::InterfaceOps for GbNode {
> + fn write(&self, data: &[u8], cport: u16, gfp_mask: Flags) -> Result<()> {
> + // SAFETY: `GbNode` only exists while its serdev driver is bound, so the device is in the
> + // `Bound` state for the duration of this call.
> + let bound: &serdev::Device<Bound> =
> + unsafe { serdev::Device::from_device(self.sdev.as_ref().as_bound()) };
[Severity: High]
Does this unsafely assume the serdev device is bound and race with driver unbind?
GbNode::write can be invoked asynchronously by the Greybus core. When the
device is removed, GbUartNode::unbind drops the subsystem state but does not
synchronize with concurrently executing write calls.
This casts the serdev device to Bound without taking any locks that would
serialize it against unbind. If write is preempted and executes concurrently
with or finishes after unbind returns, it will call bound.write_all on an
unbound or freed serdev device, leading to a Use-After-Free.
> +
> + let mut buf = KVec::with_capacity(HDLC_MAX_FRAME_LEN, gfp_mask)?;
> +
> + let mut crc = 0xffff;
> +
> + buf.push_within_capacity(HDLC_FRAME)?;
> +
> + crc = Self::fill_buf(crc, &[ADDRESS_GREYBUS, 0x03], &mut buf)?;
> + crc = Self::fill_buf(crc, &cport.to_le_bytes(), &mut buf)?;
> + crc = Self::fill_buf(crc, data, &mut buf)?;
> +
> + crc ^= 0xffff;
> + Self::fill_buf(crc, &crc.to_le_bytes(), &mut buf)?;
> +
> + buf.push_within_capacity(HDLC_FRAME)?;
> +
> + bound.write_all(&buf, 0)?;
[Severity: High]
Is it possible for concurrent calls to GbNode::write to interleave bytes here?
The Greybus subsystem can submit messages concurrently from multiple connections
or cports, leading to parallel calls to GbNode::write.
Because there is no SpinLock or Mutex in GbNode to serialize these writes,
concurrent executions of write_all will interleave bytes from different frames
at the serdev layer, completely destroying the HDLC protocol stream and
resulting in unparsable messages at the receiver.
> +
> + Ok(())
> + }
> +}
[ ... ]
> + fn unbind<'bound>(_: &'bound serdev::Device<Core<'_>>, this: Pin<&Self::Data<'bound>>) {
> + // Getting a bound device is not possible after this point. So drop HdlcRx.
> + let _ = this.rx.lock().take();
[Severity: High]
Does dropping this.rx.lock().take() directly cause a panic due to sleeping in atomic context?
In Rust, 'let _ = expr;' evaluates expr as a temporary, which drops at the end
of the statement in reverse order of creation. So the Option<HdlcRx> (created
second by take()) drops before the SpinLockGuard (created first by lock()).
Dropping HdlcRx drops gb_softsvc::Module, which deregisters the Greybus host
device. This operation sleeps and acquires mutexes. Doing this while the
spinlock is held will trigger a "scheduling while atomic" BUG or deadlock.
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-gb-uart-transport-v2-0-a03bb1f5fbd1@beagleboard.org?part=7
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom
2026-08-27 7:54 ` [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom Ayush Singh
2026-08-27 8:02 ` sashiko-bot
@ 2026-08-27 16:06 ` Conor Dooley
1 sibling, 0 replies; 14+ messages in thread
From: Conor Dooley @ 2026-08-27 16:06 UTC (permalink / raw)
To: Ayush Singh
Cc: Jason Kridner, robertcnelson, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Eric Biggers, Ard Biesheuvel,
Ayush Singh, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki, greybus-dev, linux-kernel, rust-for-linux,
linux-crypto, devicetree
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-08-27 16:06 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 7:54 [PATCH v2 0/7] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
2026-08-27 7:54 ` [PATCH v2 1/7] greybus: connection: Export gb_connection_get() and gb_connection_put() Ayush Singh
2026-08-27 7:54 ` [PATCH v2 2/7] greybus: connection: Add gb_connection_hd_find_by_intf() Ayush Singh
2026-08-27 8:13 ` sashiko-bot
2026-08-27 7:54 ` [PATCH v2 3/7] rust: crc_ccitt: add CRC-CCITT abstraction Ayush Singh
2026-08-27 7:54 ` [PATCH v2 4/7] rust: kernel: Add greybus abstractions Ayush Singh
2026-08-27 8:12 ` sashiko-bot
2026-08-27 7:54 ` [PATCH v2 5/7] drivers: greybus: Add software SVC implementation Ayush Singh
2026-08-27 8:09 ` sashiko-bot
2026-08-27 7:54 ` [PATCH v2 6/7] dt-bindings: beagle: Add BeagleConnect Freedom Ayush Singh
2026-08-27 8:02 ` sashiko-bot
2026-08-27 16:06 ` Conor Dooley
2026-08-27 7:54 ` [PATCH v2 7/7] greybus: Add Rust UART node driver Ayush Singh
2026-08-27 8:13 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).