From: Benno Lossin <benno.lossin@proton.me>
To: FUJITA Tomonori <fujita.tomonori@gmail.com>
Cc: netdev@vger.kernel.org, rust-for-linux@vger.kernel.org,
aliceryhl@google.com, andrew@lunn.ch,
miguel.ojeda.sandonis@gmail.com
Subject: Re: [PATCH 1/5] rust: core abstractions for network device drivers
Date: Sun, 25 Jun 2023 09:52:53 +0000 [thread overview]
Message-ID: <3ff6edec-c083-9294-d2df-01be983cd293@proton.me> (raw)
In-Reply-To: <20230621.221349.1237576739913195911.ubuntu@gmail.com>
On 6/21/23 15:13, FUJITA Tomonori wrote:
> Hi,
> Thanks for reviewing.
>
> On Thu, 15 Jun 2023 13:01:50 +0000
> Benno Lossin <benno.lossin@proton.me> wrote:
>
>> On 6/13/23 06:53, FUJITA Tomonori wrote:
>>> This patch adds very basic abstractions to implement network device
>>> drivers, corresponds to the kernel's net_device and net_device_ops
>>> structs with support for register_netdev/unregister_netdev functions.
>>>
>>> allows the const_maybe_uninit_zeroed feature for
>>> core::mem::MaybeUinit::<T>::zeroed() in const function.
>>>
>>> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
>>> ---
>>> rust/bindings/bindings_helper.h | 2 +
>>> rust/helpers.c | 16 ++
>>> rust/kernel/lib.rs | 3 +
>>> rust/kernel/net.rs | 5 +
>>> rust/kernel/net/dev.rs | 344 ++++++++++++++++++++++++++++++++
>>> 5 files changed, 370 insertions(+)
>>> create mode 100644 rust/kernel/net.rs
>>> create mode 100644 rust/kernel/net/dev.rs
>>>
>>> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
>>> index 3e601ce2548d..468bf606f174 100644
>>> --- a/rust/bindings/bindings_helper.h
>>> +++ b/rust/bindings/bindings_helper.h
>>> @@ -7,6 +7,8 @@
>>> */
>>>
>>> #include <linux/errname.h>
>>> +#include <linux/etherdevice.h>
>>> +#include <linux/netdevice.h>
>>> #include <linux/slab.h>
>>> #include <linux/refcount.h>
>>> #include <linux/wait.h>
>>> diff --git a/rust/helpers.c b/rust/helpers.c
>>> index bb594da56137..70d50767ff4e 100644
>>> --- a/rust/helpers.c
>>> +++ b/rust/helpers.c
>>> @@ -24,10 +24,26 @@
>>> #include <linux/errname.h>
>>> #include <linux/refcount.h>
>>> #include <linux/mutex.h>
>>> +#include <linux/netdevice.h>
>>> +#include <linux/skbuff.h>
>>> #include <linux/spinlock.h>
>>> #include <linux/sched/signal.h>
>>> #include <linux/wait.h>
>>>
>>> +#ifdef CONFIG_NET
>>> +void *rust_helper_netdev_priv(const struct net_device *dev)
>>> +{
>>> + return netdev_priv(dev);
>>> +}
>>> +EXPORT_SYMBOL_GPL(rust_helper_netdev_priv);
>>> +
>>> +void rust_helper_skb_tx_timestamp(struct sk_buff *skb)
>>> +{
>>> + skb_tx_timestamp(skb);
>>> +}
>>> +EXPORT_SYMBOL_GPL(rust_helper_skb_tx_timestamp);
>>> +#endif
>>> +
>>> __noreturn void rust_helper_BUG(void)
>>> {
>>> BUG();
>>> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
>>> index 85b261209977..fc7d048d359d 100644
>>> --- a/rust/kernel/lib.rs
>>> +++ b/rust/kernel/lib.rs
>>> @@ -13,6 +13,7 @@
>>>
>>> #![no_std]
>>> #![feature(allocator_api)]
>>> +#![feature(const_maybe_uninit_zeroed)]
>>> #![feature(coerce_unsized)]
>>> #![feature(dispatch_from_dyn)]
>>> #![feature(new_uninit)]
>>> @@ -34,6 +35,8 @@
>>> pub mod error;
>>> pub mod init;
>>> pub mod ioctl;
>>> +#[cfg(CONFIG_NET)]
>>> +pub mod net;
>>> pub mod prelude;
>>> pub mod print;
>>> mod static_assert;
>>> diff --git a/rust/kernel/net.rs b/rust/kernel/net.rs
>>> new file mode 100644
>>> index 000000000000..28fe8f398463
>>> --- /dev/null
>>> +++ b/rust/kernel/net.rs
>>> @@ -0,0 +1,5 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +
>>> +//! Networking core.
>>> +
>>> +pub mod dev;
>>> diff --git a/rust/kernel/net/dev.rs b/rust/kernel/net/dev.rs
>>> new file mode 100644
>>> index 000000000000..d072c81f99ce
>>> --- /dev/null
>>> +++ b/rust/kernel/net/dev.rs
>>> @@ -0,0 +1,344 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +
>>> +//! Network device.
>>> +//!
>>> +//! C headers: [`include/linux/etherdevice.h`](../../../../include/linux/etherdevice.h),
>>> +//! [`include/linux/ethtool.h`](../../../../include/linux/ethtool.h),
>>> +//! [`include/linux/netdevice.h`](../../../../include/linux/netdevice.h),
>>> +//! [`include/linux/skbuff.h`](../../../../include/linux/skbuff.h),
>>> +//! [`include/uapi/linux/if_link.h`](../../../../include/uapi/linux/if_link.h).
>>> +
>>> +use crate::{bindings, error::*, prelude::vtable, types::ForeignOwnable};
>>> +use {core::ffi::c_void, core::marker::PhantomData};
>>> +
>>> +/// Corresponds to the kernel's `struct net_device`.
>>> +///
>>> +/// # Invariants
>>> +///
>>> +/// The pointer is valid.
>>> +pub struct Device(*mut bindings::net_device);
>>> +
>>> +impl Device {
>>> + /// Creates a new [`Device`] instance.
>>> + ///
>>> + /// # Safety
>>> + ///
>>> + /// Callers must ensure that `ptr` must be valid.
>>> + unsafe fn from_ptr(ptr: *mut bindings::net_device) -> Self {
>>> + // INVARIANT: The safety requirements ensure the invariant.
>>> + Self(ptr)
>>> + }
>>> +
>>> + /// Gets a pointer to network device private data.
>>> + fn priv_data_ptr(&self) -> *const c_void {
>>> + // SAFETY: The type invariants guarantee that `self.0` is valid.
>>> + // During the initialization of `Registration` instance, the kernel allocates
>>> + // contiguous memory for `struct net_device` and a pointer to its private data.
>>> + // So it's safe to read an address from the returned address from `netdev_priv()`.
>>> + unsafe { core::ptr::read(bindings::netdev_priv(self.0) as *const *const c_void) }
>>
>> Why are at least `size_of::<*const c_void>` bytes allocated? Why is it a
>> `*const c_void` pointer? This function does not give any guarantees about
>> this pointer, is it valid?
>
> The reason is a device driver needs its data structure. It needs to
> access to it via a pointer to bindings::net_device struct. The space
> for the pointer is allocated during initialization of Registration and
> it's valid until the Registration object is dropped.
I think this should be encoded in the type invariants of `Device`.
Because the safety comment needs some justification.
>
>> I know that you are allocating exactly this amount in `Registration`, but
>> `Device` does not know about that. Should this be a type invariant?
>> It might be a good idea to make `Driver` generic over `D`, the data that is
>> stored behind this pointer. You could then return `D::Borrowed` instead.
>
> We could do:
>
> impl<D: DriverData> Device<D> {
> ...
> /// Gets the private data of a device driver.
> pub fn drv_priv_data(&self) -> <D::Data as ForeignOwnable>::Borrowed<'_> {
> unsafe {
> D::Data::borrow(core::ptr::read(
> bindings::netdev_priv(self.ptr) as *const *const c_void
> ))
> }
> }
> }
LGTM (+ adding a Safety comment).
>
>
>>> +// SAFETY: `Device` is just a wrapper for the kernel`s `struct net_device`, which can be used
>>> +// from any thread. `struct net_device` stores a pointer to `DriverData::Data`, which is `Sync`
>>> +// so it's safe to sharing its pointer.
>>> +unsafe impl Send for Device {}
>>> +// SAFETY: `Device` is just a wrapper for the kernel`s `struct net_device`, which can be used
>>> +// from any thread. `struct net_device` stores a pointer to `DriverData::Data`, which is `Sync`,
>>> +// can be used from any thread too.
>>> +unsafe impl Sync for Device {}
>>> +
>>> +/// Trait for device driver specific information.
>>> +///
>>> +/// This data structure is passed to a driver with the operations for `struct net_device`
>>> +/// like `struct net_device_ops`, `struct ethtool_ops`, `struct rtnl_link_ops`, etc.
>>> +pub trait DriverData {
>>> + /// The object are stored in C object, `struct net_device`.
>>> + type Data: ForeignOwnable + Send + Sync;
>>
>> Why is this an associated type? Could you not use
>> `D: ForeignOwnable + Send + Sync` everywhere instead?
>> I think this should be possible, since `DriverData` does not define
>> anything else.
>
> With that approach, is it possible to allow a device driver to define
> own data structure and functions taking the structure as aurgument
> (like DevOps structutre in the 5th patch)
>
In the example both structs are empty so I am not really sure why it has
to be two types. Can't we do this:
```
pub struct MyDriver {
// Just some random fields...
pub access_count: Cell<usize>,
}
impl DriverData for Box<MyDriver> {}
// And then we could make `DeviceOperations: DriverData`.
// Users would then do this:
#[vtable]
impl DeviceOperations for Box<MyDriver> {
fn init(_dev: Device, data: &MyDriver) -> Result {
data.access_count.set(0);
Ok(())
}
fn open(_dev: Device, data: &MyDriver) -> Result {
data.access_count.set(data.access_count.get() + 1);
Ok(())
}
}
```
I think this would simplify things, because you do not have to carry the
extra associated type around (and have to spell out
`<D::Data as ForeignOwnable>` everywhere).
>
>>> +/// Registration structure for a network device driver.
>>> +///
>>> +/// This allocates and owns a `struct net_device` object.
>>> +/// Once the `net_device` object is registered via `register_netdev` function,
>>> +/// the kernel calls various functions such as `struct net_device_ops` operations with
>>> +/// the `net_device` object.
>>> +///
>>> +/// A driver must implement `struct net_device_ops` so the trait for it is tied.
>>> +/// Other operations like `struct ethtool_ops` are optional.
>>> +pub struct Registration<T: DeviceOperations<D>, D: DriverData> {
>>> + dev: Device,
>>> + is_registered: bool,
>>> + _p: PhantomData<(D, T)>,
>>> +}
>>> +
>>> +impl<D: DriverData, T: DeviceOperations<D>> Drop for Registration<T, D> {
>>> + fn drop(&mut self) {
>>> + // SAFETY: The type invariants guarantee that `self.dev.0` is valid.
>>> + unsafe {
>>> + let _ = D::Data::from_foreign(self.dev.priv_data_ptr());
>>
>> Why is `self.dev.priv_data_ptr()` a valid pointer?
>> This `unsafe` block should be split to better explain the different safety
>> requirements.
>
> Explained above.
>
>>> + if self.is_registered {
>>> + bindings::unregister_netdev(self.dev.0);
>>> + }
>>> + bindings::free_netdev(self.dev.0);
>>> + }
>>> + }
>>> +}
>>> +
>>> +impl<D: DriverData, T: DeviceOperations<D>> Registration<T, D> {
>>> + /// Creates a new [`Registration`] instance for ethernet device.
>>> + ///
>>> + /// A device driver can pass private data.
>>> + pub fn try_new_ether(tx_queue_size: u32, rx_queue_size: u32, data: D::Data) -> Result<Self> {
>>> + // SAFETY: FFI call.
>>
>> If this FFI call has no safety requirements then say so.
>
> SAFETY: FFI call has no safety requirements.
>
> ?
Yes you should just write that.
>
>>> + const DEVICE_OPS: bindings::net_device_ops = bindings::net_device_ops {
>>> + ndo_init: if <T>::HAS_INIT {
>>> + Some(Self::init_callback)
>>> + } else {
>>> + None
>>> + },
>>> + ndo_uninit: if <T>::HAS_UNINIT {
>>> + Some(Self::uninit_callback)
>>> + } else {
>>> + None
>>> + },
>>> + ndo_open: if <T>::HAS_OPEN {
>>> + Some(Self::open_callback)
>>> + } else {
>>> + None
>>> + },
>>> + ndo_stop: if <T>::HAS_STOP {
>>> + Some(Self::stop_callback)
>>> + } else {
>>> + None
>>> + },
>>> + ndo_start_xmit: if <T>::HAS_START_XMIT {
>>> + Some(Self::start_xmit_callback)
>>> + } else {
>>> + None
>>> + },
>>> + // SAFETY: The rest is zeroed out to initialize `struct net_device_ops`,
>>> + // set `Option<&F>` to be `None`.
>>> + ..unsafe { core::mem::MaybeUninit::<bindings::net_device_ops>::zeroed().assume_init() }
>>> + };
>>> +
>>> + const fn build_device_ops() -> &'static bindings::net_device_ops {
>>> + &Self::DEVICE_OPS
>>> + }
>>
>> Why does this function exist?
>
> To get const struct net_device_ops *netdev_ops.
Can't you just use `&Self::DEVICE_OPS`?
>
>>> +
>>> + unsafe extern "C" fn init_callback(netdev: *mut bindings::net_device) -> core::ffi::c_int {
>>> + from_result(|| {
>>
>> Since you are the first user of `from_result`, you can remove the
>> `#[allow(dead_code)]` attribute.
>>
>> @Reviewers/Maintainers: Or would we prefer to make that change ourselves?
>
> Ah, either is fine by me.
>
>>> + // SAFETY: The C API guarantees that `netdev` is valid while this function is running.
>>> + let mut dev = unsafe { Device::from_ptr(netdev) };
>>> + // SAFETY: The returned pointer was initialized by `D::Data::into_foreign` when
>>> + // `Registration` object was created.
>>> + // `D::Data::from_foreign` is only called by the object was released.
>>> + // So we know `data` is valid while this function is running.
>>
>> This should be a type invariant of `Registration`.
>
> Understood.
>
>>> + let data = unsafe { D::Data::borrow(dev.priv_data_ptr()) };
>>> + T::init(&mut dev, data)?;
>>> + Ok(0)
>>> + })
>>> + }
>>> +
>>> + unsafe extern "C" fn uninit_callback(netdev: *mut bindings::net_device) {
>>> + // SAFETY: The C API guarantees that `netdev` is valid while this function is running.
>>> + let mut dev = unsafe { Device::from_ptr(netdev) };
>>> + // SAFETY: The returned pointer was initialized by `D::Data::into_foreign` when
>>> + // `Registration` object was created.
>>> + // `D::Data::from_foreign` is only called by the object was released.
>>> + // So we know `data` is valid while this function is running.
>>> + let data = unsafe { D::Data::borrow(dev.priv_data_ptr()) };
>>> + T::uninit(&mut dev, data);
>>> + }
>>> +
>>> + unsafe extern "C" fn open_callback(netdev: *mut bindings::net_device) -> core::ffi::c_int {
>>> + from_result(|| {
>>> + // SAFETY: The C API guarantees that `netdev` is valid while this function is running.
>>> + let mut dev = unsafe { Device::from_ptr(netdev) };
>>> + // SAFETY: The returned pointer was initialized by `D::Data::into_foreign` when
>>> + // `Registration` object was created.
>>> + // `D::Data::from_foreign` is only called by the object was released.
>>> + // So we know `data` is valid while this function is running.
>>> + let data = unsafe { D::Data::borrow(dev.priv_data_ptr()) };
>>> + T::open(&mut dev, data)?;
>>> + Ok(0)
>>> + })
>>> + }
>>> +
>>> + unsafe extern "C" fn stop_callback(netdev: *mut bindings::net_device) -> core::ffi::c_int {
>>> + from_result(|| {
>>> + // SAFETY: The C API guarantees that `netdev` is valid while this function is running.
>>> + let mut dev = unsafe { Device::from_ptr(netdev) };
>>> + // SAFETY: The returned pointer was initialized by `D::Data::into_foreign` when
>>> + // `Registration` object was created.
>>> + // `D::Data::from_foreign` is only called by the object was released.
>>> + // So we know `data` is valid while this function is running.
>>> + let data = unsafe { D::Data::borrow(dev.priv_data_ptr()) };
>>> + T::stop(&mut dev, data)?;
>>> + Ok(0)
>>> + })
>>> + }
>>> +
>>> + unsafe extern "C" fn start_xmit_callback(
>>> + skb: *mut bindings::sk_buff,
>>> + netdev: *mut bindings::net_device,
>>> + ) -> bindings::netdev_tx_t {
>>> + // SAFETY: The C API guarantees that `netdev` is valid while this function is running.
>>> + let mut dev = unsafe { Device::from_ptr(netdev) };
>>> + // SAFETY: The returned pointer was initialized by `D::Data::into_foreign` when
>>> + // `Registration` object was created.
>>> + // `D::Data::from_foreign` is only called by the object was released.
>>> + // So we know `data` is valid while this function is running.
>>> + let data = unsafe { D::Data::borrow(dev.priv_data_ptr()) };
>>> + // SAFETY: The C API guarantees that `skb` is valid while this function is running.
>>> + let skb = unsafe { SkBuff::from_ptr(skb) };
>>> + T::start_xmit(&mut dev, data, skb) as bindings::netdev_tx_t
>>> + }
>>> +}
>>> +
>>> +// SAFETY: `Registration` exposes only `Device` object which can be used from
>>> +// any thread.
>>> +unsafe impl<D: DriverData, T: DeviceOperations<D>> Send for Registration<T, D> {}
>>> +// SAFETY: `Registration` exposes only `Device` object which can be used from
>>> +// any thread.
>>> +unsafe impl<D: DriverData, T: DeviceOperations<D>> Sync for Registration<T, D> {}
>>> +
>>> +/// Corresponds to the kernel's `enum netdev_tx`.
>>> +#[repr(i32)]
>>> +pub enum TxCode {
>>> + /// Driver took care of packet.
>>> + Ok = bindings::netdev_tx_NETDEV_TX_OK,
>>> + /// Driver tx path was busy.
>>> + Busy = bindings::netdev_tx_NETDEV_TX_BUSY,
>>> +}
>>> +
>>> +/// Corresponds to the kernel's `struct net_device_ops`.
>>> +///
>>> +/// A device driver must implement this. Only very basic operations are supported for now.
>>> +#[vtable]
>>> +pub trait DeviceOperations<D: DriverData> {
>>
>> Why is this trait generic over `D`? Why is this not `Self` or an associated
>> type?
>
> DriverData also used in EtherOperationsAdapter (the second patch) and
> there are other operations that uses DriverData (not in this patchset).
Could you point me to those other things that use `DriverData`?
>
>
>>> + /// Corresponds to `ndo_init` in `struct net_device_ops`.
>>> + fn init(_dev: &mut Device, _data: <D::Data as ForeignOwnable>::Borrowed<'_>) -> Result {
>>
>> Why do all of these functions take a `&mut Device`? `Device` already is a
>> pointer, so why the double indirection?
>
> I guess that I follow the existing code like
>
> https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/amba.rs
I do not really see a reason why it should be `&mut Device` over just
`Device`.
--
Cheers,
Benno
>
>>> +/// Corresponds to the kernel's `struct sk_buff`.
>>> +///
>>> +/// A driver manages `struct sk_buff` in two ways. In both ways, the ownership is transferred
>>> +/// between C and Rust. The allocation and release are done asymmetrically.
>>> +///
>>> +/// On the tx side (`ndo_start_xmit` operation in `struct net_device_ops`), the kernel allocates
>>> +/// a `sk_buff' object and passes it to the driver. The driver is responsible for the release
>>> +/// after transmission.
>>> +/// On the rx side, the driver allocates a `sk_buff` object then passes it to the kernel
>>> +/// after receiving data.
>>> +///
>>> +/// # Invariants
>>> +///
>>> +/// The pointer is valid.
>>> +pub struct SkBuff(*mut bindings::sk_buff);
>>> +
>>> +impl SkBuff {
>>> + /// Creates a new [`SkBuff`] instance.
>>> + ///
>>> + /// # Safety
>>> + ///
>>> + /// Callers must ensure that `ptr` must be valid.
>>> + unsafe fn from_ptr(ptr: *mut bindings::sk_buff) -> Self {
>>> + // INVARIANT: The safety requirements ensure the invariant.
>>> + Self(ptr)
>>> + }
>>> +
>>> + /// Provides a time stamp.
>>> + pub fn tx_timestamp(&mut self) {
>>> + // SAFETY: The type invariants guarantee that `self.0` is valid.
>>> + unsafe {
>>> + bindings::skb_tx_timestamp(self.0);
>>> + }
>>> + }
>>> +}
>>> +
>>> +impl Drop for SkBuff {
>>> + fn drop(&mut self) {
>>> + // SAFETY: The type invariants guarantee that `self.0` is valid.
>>> + unsafe {
>>> + bindings::kfree_skb_reason(
>>> + self.0,
>>> + bindings::skb_drop_reason_SKB_DROP_REASON_NOT_SPECIFIED,
>>> + )
>>
>> AFAICT this function frees the `struct sk_buff`, why is this safe? This
>> function also has as a requirement that all other pointers to this struct
>> are never used again. How do you guarantee this?
>> You mentioned above that there are two us cases for an SkBuff, in one case
>> the kernel frees it and in another the driver. How do we know that we can
>> free it here?
>
> This can handle only the tx case.
>
> As you can see, we had a good discussion on this and seems that found
> a solution. It'll be fixed.
>
next prev parent reply other threads:[~2023-06-25 9:53 UTC|newest]
Thread overview: 74+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-06-13 4:53 [PATCH 0/5] Rust abstractions for network device drivers FUJITA Tomonori
2023-06-13 4:53 ` [PATCH 1/5] rust: core " FUJITA Tomonori
2023-06-15 13:01 ` Benno Lossin
2023-06-21 13:13 ` FUJITA Tomonori
2023-06-25 9:52 ` Benno Lossin [this message]
2023-06-25 14:27 ` FUJITA Tomonori
2023-06-25 17:06 ` Benno Lossin
2023-06-21 22:44 ` Boqun Feng
2023-06-22 0:19 ` FUJITA Tomonori
2023-06-13 4:53 ` [PATCH 2/5] rust: add support for ethernet operations FUJITA Tomonori
2023-06-13 7:19 ` Ariel Miculas
2023-06-15 13:03 ` Benno Lossin
2023-06-15 13:44 ` Andrew Lunn
2023-06-13 4:53 ` [PATCH 3/5] rust: add support for get_stats64 in struct net_device_ops FUJITA Tomonori
2023-06-13 4:53 ` [PATCH 4/5] rust: add methods for configure net_device FUJITA Tomonori
2023-06-15 13:06 ` Benno Lossin
2023-06-13 4:53 ` [PATCH 5/5] samples: rust: add dummy network driver FUJITA Tomonori
2023-06-15 13:08 ` Benno Lossin
2023-06-22 0:23 ` FUJITA Tomonori
2023-06-15 6:01 ` [PATCH 0/5] Rust abstractions for network device drivers Jakub Kicinski
2023-06-15 8:58 ` Miguel Ojeda
2023-06-16 2:19 ` Jakub Kicinski
2023-06-16 12:18 ` FUJITA Tomonori
2023-06-16 13:23 ` Miguel Ojeda
2023-06-16 13:41 ` FUJITA Tomonori
2023-06-16 18:26 ` Jakub Kicinski
2023-06-16 20:05 ` Miguel Ojeda
2023-06-16 13:04 ` Andrew Lunn
2023-06-16 18:31 ` Jakub Kicinski
2023-06-16 13:18 ` Miguel Ojeda
2023-06-15 12:51 ` Andrew Lunn
2023-06-16 2:02 ` Jakub Kicinski
2023-06-16 3:47 ` Richard Cochran
2023-06-16 17:59 ` Andrew Lunn
2023-06-16 13:02 ` FUJITA Tomonori
2023-06-16 13:14 ` Andrew Lunn
2023-06-16 13:48 ` Miguel Ojeda
2023-06-16 14:43 ` Andrew Lunn
2023-06-16 16:01 ` Miguel Ojeda
2023-06-19 11:27 ` Emilio Cobos Álvarez
2023-06-20 18:09 ` Miguel Ojeda
2023-06-20 19:12 ` Andreas Hindborg (Samsung)
2023-06-21 12:30 ` Andreas Hindborg (Samsung)
2023-06-16 18:40 ` Jakub Kicinski
2023-06-16 19:00 ` Alice Ryhl
2023-06-16 19:10 ` Jakub Kicinski
2023-06-16 19:23 ` Alice Ryhl
2023-06-16 20:04 ` Andrew Lunn
2023-06-17 10:08 ` Alice Ryhl
2023-06-17 10:15 ` Greg KH
2023-06-19 8:50 ` FUJITA Tomonori
2023-06-19 9:46 ` Greg KH
2023-06-19 11:05 ` FUJITA Tomonori
2023-06-19 11:14 ` Greg KH
2023-06-19 13:20 ` Andrew Lunn
2023-06-20 11:16 ` David Laight
2023-06-20 15:47 ` Jakub Kicinski
2023-06-20 16:56 ` Alice Ryhl
2023-06-20 17:44 ` Miguel Ojeda
2023-06-20 17:55 ` Miguel Ojeda
2023-06-16 12:28 ` Alice Ryhl
2023-06-16 13:20 ` Andrew Lunn
2023-06-16 13:24 ` Alice Ryhl
[not found] <20230604021736.3392963-1-tomo@exabit.dev>
2023-06-04 2:17 ` [PATCH 1/5] rust: core " FUJITA Tomonori
2023-06-04 12:47 ` Alice Ryhl
2023-06-05 13:36 ` FUJITA Tomonori
2023-06-08 7:38 ` Alice Ryhl
2023-06-09 12:39 ` FUJITA Tomonori
2023-06-09 13:15 ` Alice Ryhl
2023-06-09 13:23 ` FUJITA Tomonori
2023-06-06 16:20 ` Andrew Lunn
2023-06-07 5:14 ` FUJITA Tomonori
2023-06-08 8:10 ` Alice Ryhl
2023-06-09 12:52 ` FUJITA Tomonori
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=3ff6edec-c083-9294-d2df-01be983cd293@proton.me \
--to=benno.lossin@proton.me \
--cc=aliceryhl@google.com \
--cc=andrew@lunn.ch \
--cc=fujita.tomonori@gmail.com \
--cc=miguel.ojeda.sandonis@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=rust-for-linux@vger.kernel.org \
/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;
as well as URLs for NNTP newsgroup(s).