Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Ayush Singh" <ayush@beagleboard.org>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org,
	ojeda@kernel.org, gary@garyguo.net
Subject: Re: [PATCH v4 4/6] drivers: greybus: Add software SVC implementation
Date: Wed, 21 Oct 2026 11:57:49 +0000	[thread overview]
Message-ID: <sashiko-outbox-180197@kernel.org> (raw)
In-Reply-To: <20261021-gb-uart-transport-v4-4-c308ce821d45@beagleboard.org>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] greybus: gb_softsvc: synchronous data_rcvd() in message_send() races timer
- [High] greybus: gb_softsvc: GLOBAL_STATE published before SVC handshake in init()
- [High] greybus: gb_softsvc: premature intf_map removal and race in module_remove()
- [Medium] greybus: gb_softsvc: drop under spinlock and non-atomic loop in map_remove()
- [Medium] greybus: gb_softsvc: Svc::handler() leaks internal ENOTSUPP to userspace
- [Low] greybus: gb_softsvc: stale API docs and wrong constant in refclk helper
--

commit 911de474c435921e03dc8049b26a79b5e9a04e61
Author: Ayush Singh <ayush@beagleboard.org>

drivers: greybus: Add software SVC implementation

This commit adds gb-softsvc, an in-kernel Rust implementation of the Greybus
supervisory controller for transports that do not have a hardware controller
on the bus. It registers a Greybus host device and exposes an interface for
transport drivers to attach modules and exchange Greybus messages.

> diff --git a/drivers/greybus/gb_softsvc.rs b/drivers/greybus/gb_softsvc.rs
> new file mode 100644
> index 0000000000000..f88d9b3fd5655
> --- /dev/null
> +++ b/drivers/greybus/gb_softsvc.rs
> @@ -0,0 +1,520 @@

[ ... ]

> +impl Svc {

[ ... ]

> +    fn map_remove(&self, primary_id: u8, intf_count: u8, dev: &kernel::device::Device) {
> +        let id = match primary_id.checked_sub(INTF_ID_START) {
> +            Some(x) => x,
> +            None => {
> +                dev_warn!(dev, "Invalid interace id");
> +                return;
> +            }
> +        };
> +
> +        for i in 0..intf_count {
> +            let _ = self.intf_map.lock()[usize::from(id + i)].take();
> +        }
> +    }

[Severity: Medium]
In Svc::map_remove(), does the loop drop the removed Arc<dyn InterfaceOps>
while holding the intf_map spinlock, and clear multi-interface ranges
non-atomically?

Because let _ = self.intf_map.lock()[usize::from(id + i)].take(); uses a
wildcard pattern that does not bind the returned Option<Arc<dyn InterfaceOps>>,
Rust drops the temporary Option<Arc<dyn InterfaceOps>> at the end of the
initializer expression before dropping the statement-scoped temporary spinlock
Guard returned by self.intf_map.lock() at the semicolon. When intf_map holds
the last Arc reference, the transport driver's InterfaceOps destructor and
kfree run inside the SpinLock section.

In addition, acquiring and releasing self.intf_map on each iteration of the
0..intf_count loop rather than holding the lock across the entire range allows
a concurrent Svc::map_insert() to observe a partially removed multi-interface
window.

[ ... ]

> +    fn module_remove(&self, hd: &hd::Device, intf_id: u8, intf_count: u8) -> Result<()> {
> +        let payload = protocols::GbSvcModuleRemovedRequest::new(intf_id);
> +        let res = self.send_request(hd, protocols::GB_SVC_TYPE_MODULE_REMOVED, payload);
> +
> +        if res.is_err() {
> +            dev_warn!(
> +                hd.as_ref(),
> +                "Failed to send module_removed svc request for {}",
> +                intf_id
> +            );
> +        }
> +
> +        self.map_remove(intf_id, intf_count, hd.as_ref());
> +
> +        Ok(())
> +    }

[Severity: High]
When Module::drop() calls Svc::module_remove(), can clearing self.intf_map
immediately after queuing GB_SVC_TYPE_MODULE_REMOVED race with asynchronous
Greybus core teardown and concurrent GbHdDriver::message_send() calls?

Svc::send_request() calls hd.data_rcvd(), which only queues operation->work
onto svc->connection->wq, and gb_svc_request_handler() then queues
gb_svc_process_module_removed() onto svc->wq. Until
gb_svc_process_module_removed() runs gb_module_del() on svc->wq, the old
module's struct gb_module, struct gb_interface, and struct gb_connection
objects remain active in hd->modules and hd->connections:

1. If Module::new() is called before gb_svc_process_module_removed() finishes
(or after send_request() in Svc::module_remove() fails), Svc::map_insert() can
reallocate the cleared intf_id to the new module while the old module is still
registered. Outbound messages on the old module's connections then look up
intf_by_id(intf_id) in GbHdDriver::message_send() and are misrouted to the new
module's InterfaceOps::write(), inbound messages from the new module via
Module::submit_message() match the old module's connections in
hd->connections, and gb_svc_process_module_inserted() rejects the new module
if the old gb_module is still present in hd->modules.

2. While gb_module_del() tears down the old module's connections on svc->wq,
clearing intf_map beforehand causes outgoing teardown messages to fail with
ENODEV.

3. In GbHdDriver::message_send(), data.0.intf_by_id() clones the
Arc<dyn InterfaceOps> and drops the intf_map spinlock before calling
intf.write(&buf, conn.intf_cport_id(), gfp_mask). Because Module::drop() does
not synchronize with in-flight GbHdDriver::message_send() calls or wait for
connection teardown, InterfaceOps::write() can still execute after
Module::drop() returns, contrary to the doc comment on impl Drop for Module.

[ ... ]

> +    fn send_response<P: AsBytes>(
> +        &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());
> +    }

[Severity: Critical]
When GbHdDriver::message_send() handles a request for GB_SVC_CPORT_ID by
synchronously calling Svc::handler() -> Svc::send_response() ->
hd.data_rcvd(), can the response completion race with timer setup in
gb_operation_request_send() and leave an active timer on a freed
struct gb_operation?

Every synchronous request sent by the Greybus core with a non-zero timeout
(such as gb_svc_pwrmon_rail_count_get(), gb_svc_intf_device_id(),
gb_svc_intf_set_power_mode(), gb_svc_intf_vsys_set(),
gb_svc_intf_refclk_set(), gb_svc_intf_unipro_set(), gb_svc_intf_activate(),
gb_svc_dme_peer_get(), gb_svc_dme_peer_set(), gb_svc_route_create(), and
gb_svc_connection_create()) calls gb_operation_sync_timeout() ->
gb_operation_request_send().

In drivers/greybus/operation.c:gb_operation_request_send(), gb_message_send()
is invoked before operation->timer is armed:

drivers/greybus/operation.c:gb_operation_request_send() {
    ...
	ret = gb_message_send(operation->request, gfp);
	if (ret)
		goto err_put_active;

	if (timeout) {
		operation->timer.expires = jiffies + msecs_to_jiffies(timeout);
		add_timer(&operation->timer);
	}
    ...
}

Because Svc::send_response() calls hd.data_rcvd() synchronously inside
GbHdDriver::message_send() before gb_message_send() returns,
gb_connection_recv_response() immediately queues operation->work
(gb_operation_work()) onto gb_operation_completion_wq (WQ_PERCPU):

drivers/greybus/operation.c:gb_operation_work() {
    ...
	} else {
		ret = timer_delete_sync(&operation->timer);
		if (!ret) {
			/* Cancel request message if scheduled by timeout. */
			if (gb_operation_result(operation) == -ETIMEDOUT)
				gb_message_cancel(operation->request);
		}

		operation->callback(operation);
	}

	gb_operation_put_active(operation);
	gb_operation_put(operation);
}

If gb_operation_work() runs on another processor or preempts the sending
thread before gb_operation_request_send() reaches add_timer(&operation->timer),
timer_delete_sync(&operation->timer) runs while the timer is still unarmed,
operation->callback() completes operation->completion, and the active
reference is dropped.

When the sending thread resumes in gb_operation_request_send(), it calls
add_timer(&operation->timer) on the already-completed operation;
wait_for_completion_interruptible() then returns immediately and
gb_operation_sync_timeout() calls gb_operation_put(operation), freeing
struct gb_operation to gb_operation_cache while operation->timer remains
queued in the timer wheel and causing a use-after-free in
gb_operation_timeout() when the timer expires.

[ ... ]

> +    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);
> +    }

[Severity: Low]
This isn't a runtime bug since GB_SVC_INTF_VSYS_OK and GB_SVC_INTF_REFCLK_OK
both evaluate to 0x00, but should Svc::intf_refclk_enable_disable() pass
protocols::GB_SVC_INTF_REFCLK_OK to protocols::GbSvcIntfRefclkResponse::new()?

There are also a few minor comment and documentation mismatches in
drivers/greybus/gb_softsvc.rs and the commit description:

1. The commit description and the comments in Svc::conn_create() and
Svc::conn_destroy() refer to NodeOps, module_insert(), and module_remove(),
whereas the public trait and lifecycle interface are InterfaceOps,
Module::new(), and Drop for Module.

2. The doc comment on Module::new() refers to parameter node (now intfs) and
states that it returns the interface id rather than Result<Self>, while the
doc comment on Module::submit_message() states that it fails with EAGAIN if
the host device has not been brought up yet (which is checked in Module::new()
via GLOBAL_STATE, whereas Module::submit_message() calls
hd::Device::data_rcvd_by_intf() and only returns Ok(()) or EINVAL).

3. Svc::map_remove() logs "Invalid interace id" (missing 'f' in "interface").

[ ... ]

> +            (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(())
> +    }
> +}

[Severity: Medium]
Should the fallback arm in Svc::handler() return a standard user space error
code such as EOPNOTSUPP or EPROTONOSUPPORT instead of internal kernel errno
ENOTSUPP (524)?

When gb_softsvc sends GB_SVC_TYPE_SVC_HELLO and attaches a module via
Module::new() -> Svc::module_insert(), the Greybus core exposes the
intf_eject sysfs attribute on the greybus_svc device, the eject sysfs
attribute on the greybus_module device, and (because Svc::intf_activate()
reports GB_SVC_INTF_TYPE_GREYBUS and interface_power_is_visible() in
drivers/greybus/interface.c checks only intf->type) the voltage_now,
current_now, and power_now sysfs attributes on the interface device.

When user space reads voltage_now, current_now, or power_now or writes to
eject or intf_eject, the Greybus core sends
GB_SVC_TYPE_PWRMON_INTF_SAMPLE_GET or GB_SVC_TYPE_INTF_EJECT synchronously via
gb_operation_sync_timeout() -> gb_operation_request_send_sync_timeout() ->
gb_operation_request_send() -> gb_message_send() ->
HdDriverVTable::<GbHdDriver>::message_send() -> GbHdDriver::message_send() ->
Svc::handler().

Unlike wire responses that pass through gb_operation_status_map(), the
Err(code::ENOTSUPP) returned from GbHdDriver::message_send() is propagated
directly by gb_operation_request_send() to the sysfs callbacks, leaking -524
(-ENOTSUPP) to user space.

> +
> +struct GbHdDriver(Arc<Svc>);
> +
> +#[vtable]
> +impl hd::HdDriver for GbHdDriver {
> +    fn message_send(
> +        data: &Self,
> +        hd: &hd::Device,
> +        dest_cport_id: u16,
> +        msg: &greybus::Message,
> +        gfp_mask: Flags,
> +    ) -> Result {
> +        if dest_cport_id == protocols::GB_SVC_CPORT_ID {
> +            data.0.handler(hd, 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);

[Severity: High]
In GreybusSoftSvc::init(), is GLOBAL_STATE published too early, before the
asynchronous GB_SVC_TYPE_PROTOCOL_VERSION and GB_SVC_TYPE_SVC_HELLO handshake
completes?

svc.send_version(hd.as_ref()) calls hd.data_rcvd(GB_SVC_CPORT_ID, ...), which
invokes gb_connection_recv_request() and only queues operation->work onto the
ordered workqueue svc->connection->wq without processing it synchronously.

In drivers/greybus/svc.c:gb_svc_request_handler(), the state machine remains
in GB_SVC_STATE_RESET until the work item for GB_SVC_TYPE_PROTOCOL_VERSION
runs, sends its response back to Svc::handler() to queue
Svc::send_svc_hello(), and the subsequent work item for GB_SVC_TYPE_SVC_HELLO
transitions svc->state to GB_SVC_STATE_SVC_HELLO:

drivers/greybus/svc.c:gb_svc_request_handler() {
    ...
	switch (type) {
	case GB_SVC_TYPE_PROTOCOL_VERSION:
		if (svc->state != GB_SVC_STATE_RESET)
			ret = -EINVAL;
		break;
	case GB_SVC_TYPE_SVC_HELLO:
		if (svc->state != GB_SVC_STATE_PROTOCOL_VERSION)
			ret = -EINVAL;
		break;
	default:
		if (svc->state != GB_SVC_STATE_SVC_HELLO)
			ret = -EINVAL;
		break;
	}
    ...
}

If a consumer driver calls Module::new() after GLOBAL_STATE is populated in
GreybusSoftSvc::init() but before the GB_SVC_TYPE_PROTOCOL_VERSION response
triggers Svc::send_svc_hello(), Module::new() sees GLOBAL_STATE is Some
(instead of returning EAGAIN) and calls state.svc.module_insert(), queuing
GB_SVC_TYPE_MODULE_INSERTED onto svc->connection->wq ahead of
GB_SVC_TYPE_SVC_HELLO.

When gb_svc_request_handler() processes GB_SVC_TYPE_MODULE_INSERTED while
svc->state != GB_SVC_STATE_SVC_HELLO, it rejects the request with -EINVAL.
Because Svc::handler() ignores the response status for
GB_SVC_TYPE_MODULE_INSERTED, Module::new() returns Ok(Module) while no
gb_module or gb_interface is created in the Greybus core, and the allocated
interface ids and Arc<dyn InterfaceOps> entries remain occupied in
Svc::intf_map until Module is dropped.

[ ... ]

> +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(),
> +        })
> +    }

[ ... ]

> +/// Dropping the module drops all the interfaces of the module. So none of the interface ops should
> +/// be called after this point.
> +impl Drop for Module {
> +    fn drop(&mut self) {
> +        let _ = self
> +            .svc
> +            .module_remove(&self.gb_hd, self.id, self.intf_count);
> +    }
> +}

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-gb-uart-transport-v4-0-c308ce821d45@beagleboard.org?part=4

  reply	other threads:[~2026-10-21 11:57 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 11:37 [PATCH v4 0/6] Add Greybus Sotfsvc and UART Node drivers Ayush Singh
2026-10-21 11:37 ` [PATCH v4 1/6] greybus: connection: Add greybus_data_rcvd_by_intf() Ayush Singh
2026-10-21 11:37 ` [PATCH v4 2/6] rust: crc_ccitt: add CRC-CCITT abstraction Ayush Singh
2026-10-21 11:37 ` [PATCH v4 3/6] rust: kernel: Add greybus abstractions Ayush Singh
2026-10-21 11:55   ` sashiko-bot
2026-10-21 11:37 ` [PATCH v4 4/6] drivers: greybus: Add software SVC implementation Ayush Singh
2026-10-21 11:57   ` sashiko-bot [this message]
2026-10-21 11:37 ` [PATCH v4 5/6] dt-bindings: beagle: Add BeagleConnect Freedom Ayush Singh
2026-10-21 11:37 ` [PATCH v4 6/6] greybus: Add Rust UART node driver Ayush Singh
2026-10-21 11:57   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-180197@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=ayush@beagleboard.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gary@garyguo.net \
    --cc=ojeda@kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox