Another option would be to call u32::swap_bytes() on the data being
read/written, but these helpers make the Rust code as ergonomic as the C
code.
Signed-off-by: Link Mauve <linkmauve@linkmauve.fr>
---
rust/helpers/io.c | 34 ++++++++++++++++++++++++++++++++++
rust/kernel/io.rs | 18 ++++++++++++++++++
2 files changed, 52 insertions(+)
diff --git a/rust/helpers/io.c b/rust/helpers/io.c
index c475913c69e6..514ad0377327 100644
--- a/rust/helpers/io.c
+++ b/rust/helpers/io.c
@@ -40,6 +40,23 @@ u64 rust_helper_readq(const void __iomem *addr)
}
#endif
+u16 rust_helper_ioread16be(const void __iomem *addr)
+{
+ return ioread16be(addr);
+}
+
+u32 rust_helper_ioread32be(const void __iomem *addr)
+{
+ return ioread32be(addr);
+}
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_ioread64be(const void __iomem *addr)
+{
+ return ioread64be(addr);
+}
+#endif
+
void rust_helper_writeb(u8 value, void __iomem *addr)
{
writeb(value, addr);
@@ -62,6 +79,23 @@ void rust_helper_writeq(u64 value, void __iomem *addr)
}
#endif
+void rust_helper_iowrite16be(u16 value, void __iomem *addr)
+{
+ iowrite16be(value, addr);
+}
+
+void rust_helper_iowrite32be(u32 value, void __iomem *addr)
+{
+ iowrite32be(value, addr);
+}
+
+#ifdef CONFIG_64BIT
+void rust_helper_iowrite64be(u64 value, void __iomem *addr)
+{
+ iowrite64be(value, addr);
+}
+#endif
+
u8 rust_helper_readb_relaxed(const void __iomem *addr)
{
return readb_relaxed(addr);
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 98e8b84e68d1..e6912e7ff036 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -256,6 +256,15 @@ fn io_addr_assert<U>(&self, offset: usize) -> usize {
readq -> u64
);
+ define_read!(read16be, try_read16be, ioread16be -> u16);
+ define_read!(read32be, try_read32be, ioread32be -> u32);
+ define_read!(
+ #[cfg(CONFIG_64BIT)]
+ read64be,
+ try_read64be,
+ ioread64be -> u64
+ );
+
define_read!(read8_relaxed, try_read8_relaxed, readb_relaxed -> u8);
define_read!(read16_relaxed, try_read16_relaxed, readw_relaxed -> u16);
define_read!(read32_relaxed, try_read32_relaxed, readl_relaxed -> u32);
@@ -276,6 +285,15 @@ fn io_addr_assert<U>(&self, offset: usize) -> usize {
writeq <- u64
);
+ define_write!(write16be, try_write16be, iowrite16be <- u16);
+ define_write!(write32be, try_write32be, iowrite32be <- u32);
+ define_write!(
+ #[cfg(CONFIG_64BIT)]
+ write64be,
+ try_write64be,
+ iowrite64be <- u64
+ );
+
define_write!(write8_relaxed, try_write8_relaxed, writeb_relaxed <- u8);
define_write!(write16_relaxed, try_write16_relaxed, writew_relaxed <- u16);
define_write!(write32_relaxed, try_write32_relaxed, writel_relaxed <- u32);
--
2.52.0
On Wed Feb 4, 2026 at 5:04 AM CET, Link Mauve wrote: > Another option would be to call u32::swap_bytes() on the data being > read/written, but these helpers make the Rust code as ergonomic as the C > code. > > Signed-off-by: Link Mauve <linkmauve@linkmauve.fr> The I/O stuff recently changed quite significantly, please have a look at the driver-core-next branch [1] in the driver-core tree. Also, instead of providing additional *be() methods, we should just create a new type io::Endianness and use it to indicate the device endianness when requesting the I/O resource. For instance, for your driver we could have request.iomap_exclusive_sized::<8>(Endianness::Big)? and then let the I/O backend choose the correct accessors based on this. I.e. the device is either big or little endian, hence we don't need to provide both accessors at the same time. [1] https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/log/?h=driver-core-next
> On 4 Feb 2026, at 12:18, Danilo Krummrich <dakr@kernel.org> wrote: > > On Wed Feb 4, 2026 at 5:04 AM CET, Link Mauve wrote: >> Another option would be to call u32::swap_bytes() on the data being >> read/written, but these helpers make the Rust code as ergonomic as the C >> code. >> >> Signed-off-by: Link Mauve <linkmauve@linkmauve.fr> > > The I/O stuff recently changed quite significantly, please have a look at the > driver-core-next branch [1] in the driver-core tree. > > Also, instead of providing additional *be() methods, we should just create a new > type io::Endianness and use it to indicate the device endianness when requesting > the I/O resource. > > For instance, for your driver we could have > > request.iomap_exclusive_sized::<8>(Endianness::Big)? Can we please structure this in a way that LittleEndian is the default? Perhaps using a const generic that is defaulted, or something along these lines. > > and then let the I/O backend choose the correct accessors based on this. > > I.e. the device is either big or little endian, hence we don't need to provide > both accessors at the same time. > > [1] https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/log/?h=driver-core-next >
On Thu Feb 5, 2026 at 2:28 PM GMT, Daniel Almeida wrote: > > >> On 4 Feb 2026, at 12:18, Danilo Krummrich <dakr@kernel.org> wrote: >> >> On Wed Feb 4, 2026 at 5:04 AM CET, Link Mauve wrote: >>> Another option would be to call u32::swap_bytes() on the data being >>> read/written, but these helpers make the Rust code as ergonomic as the C >>> code. >>> >>> Signed-off-by: Link Mauve <linkmauve@linkmauve.fr> >> >> The I/O stuff recently changed quite significantly, please have a look at the >> driver-core-next branch [1] in the driver-core tree. >> >> Also, instead of providing additional *be() methods, we should just create a new >> type io::Endianness and use it to indicate the device endianness when requesting >> the I/O resource. >> >> For instance, for your driver we could have >> >> request.iomap_exclusive_sized::<8>(Endianness::Big)? > > Can we please structure this in a way that LittleEndian is the default? > Perhaps using a const generic that is defaulted, or something along these lines. I think we should have everything default to little endian, and have wrapper types that do big endian which require expicit construction, similar to RelaxedMmio in Alex's series. Best, Gary > >> >> and then let the I/O backend choose the correct accessors based on this. >> >> I.e. the device is either big or little endian, hence we don't need to provide >> both accessors at the same time. >> >> [1] https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/log/?h=driver-core-next >>
> On 5 Feb 2026, at 12:16, Gary Guo <gary@garyguo.net> wrote: > > On Thu Feb 5, 2026 at 2:28 PM GMT, Daniel Almeida wrote: >> >> >>> On 4 Feb 2026, at 12:18, Danilo Krummrich <dakr@kernel.org> wrote: >>> >>> On Wed Feb 4, 2026 at 5:04 AM CET, Link Mauve wrote: >>>> Another option would be to call u32::swap_bytes() on the data being >>>> read/written, but these helpers make the Rust code as ergonomic as the C >>>> code. >>>> >>>> Signed-off-by: Link Mauve <linkmauve@linkmauve.fr> >>> >>> The I/O stuff recently changed quite significantly, please have a look at the >>> driver-core-next branch [1] in the driver-core tree. >>> >>> Also, instead of providing additional *be() methods, we should just create a new >>> type io::Endianness and use it to indicate the device endianness when requesting >>> the I/O resource. >>> >>> For instance, for your driver we could have >>> >>> request.iomap_exclusive_sized::<8>(Endianness::Big)? >> >> Can we please structure this in a way that LittleEndian is the default? >> Perhaps using a const generic that is defaulted, or something along these lines. > > I think we should have everything default to little endian, and have wrapper > types that do big endian which require expicit construction, similar to > RelaxedMmio in Alex's series. > > Best, > Gary > Ah yes, the RelaxedMmio pattern is definitely a good one. I agree that we should head in this direction. — Daniel
On Thu Feb 5, 2026 at 6:28 PM CET, Daniel Almeida wrote:
>> On 5 Feb 2026, at 12:16, Gary Guo <gary@garyguo.net> wrote:
>> I think we should have everything default to little endian, and have wrapper
>> types that do big endian which require expicit construction, similar to
>> RelaxedMmio in Alex's series.
>
> Ah yes, the RelaxedMmio pattern is definitely a good one. I agree that we
> should head in this direction.
I strongly disagree.
This is a great pattern for relaxed ordering because:
(1) We need both strict and relaxed ordering.
(2) Relaxed ordering is rare, hence it doesn't hurt to write e.g.
io.relaxed().write()
(3) If you by accident just write
io.write()
i.e. forget to call relaxed() it s not a bug, nothing bad happens.
Whereas for endianness it is a bad pattern because:
(1) Devices are either little-endian or big-endian. Hence, having to write
io.big_endian().write()
is excessive, we always want big-endian for a big-endian device.
(2) It is error prone, if you forget to call big_endian() first, it is a bug.
(3) It is unergonomic in combination with relaxed ordering.
io.big_endian().relaxed().write()
(Does the other way around work as well? :)
It makes much more sense to define once when we request the I/O memory whether
the device is litte-endian or big-endian.
This could be done with different request functions, a const generic or a
function argument, but it should be done at request time.
On Thu Feb 5, 2026 at 7:05 PM GMT, Danilo Krummrich wrote: > On Thu Feb 5, 2026 at 6:28 PM CET, Daniel Almeida wrote: >>> On 5 Feb 2026, at 12:16, Gary Guo <gary@garyguo.net> wrote: >>> I think we should have everything default to little endian, and have wrapper >>> types that do big endian which require expicit construction, similar to >>> RelaxedMmio in Alex's series. >> >> Ah yes, the RelaxedMmio pattern is definitely a good one. I agree that we >> should head in this direction. > > I strongly disagree. > > This is a great pattern for relaxed ordering because: > > (1) We need both strict and relaxed ordering. > > (2) Relaxed ordering is rare, hence it doesn't hurt to write e.g. > > io.relaxed().write() > > (3) If you by accident just write > > io.write() > > i.e. forget to call relaxed() it s not a bug, nothing bad happens. > > Whereas for endianness it is a bad pattern because: > > (1) Devices are either little-endian or big-endian. Hence, having to write > > io.big_endian().write() > > is excessive, we always want big-endian for a big-endian device. You don't need to always write this. You just need to do `big_endian()` once when you obtain the io, and then keep using `BigEndian<Mmio>` instead of just `Mmio`, and the rest of code is still `.write()`. I proposed the wrapper type because majority of devices won't need BE support, so adding complexity to Mmio itself is not ideal. It is also generic, so it can work with any IO backends, so for example, you can have `BigEndian<Pio>` and `BigEndian<Mmio>` and you don't need to duplicate your endianness support for both backends. > > (2) It is error prone, if you forget to call big_endian() first, it is a bug. Moot point when `big_endian()` is only done once. > > (3) It is unergonomic in combination with relaxed ordering. > > io.big_endian().relaxed().write() This might be an issue, as `RelaxedMmio`, unless `BigEndian`, cannot be implemented as wrapper that just reverse byteorder. Although I am not sure that we even need that support, given that there's no be_relaxed functions on C side anyway. Best, Gary > > (Does the other way around work as well? :) > > It makes much more sense to define once when we request the I/O memory whether > the device is litte-endian or big-endian. > > This could be done with different request functions, a const generic or a > function argument, but it should be done at request time.
On Thu Feb 5, 2026 at 11:31 PM CET, Gary Guo wrote: > I proposed the wrapper type because majority of devices won't need BE support, > so adding complexity to Mmio itself is not ideal. It is also generic, so it can > work with any IO backends, so for example, you can have `BigEndian<Pio>` and > `BigEndian<Mmio>` and you don't need to duplicate your endianness support for > both backends. That implies that we swap bytes manually? That would be a waste if the CPU and device are big-endian.
On Thu Feb 5, 2026 at 11:31 PM CET, Gary Guo wrote: > On Thu Feb 5, 2026 at 7:05 PM GMT, Danilo Krummrich wrote: >> (1) Devices are either little-endian or big-endian. Hence, having to write >> >> io.big_endian().write() >> >> is excessive, we always want big-endian for a big-endian device. > > You don't need to always write this. You just need to do `big_endian()` once > when you obtain the io, and then keep using `BigEndian<Mmio>` instead of just > `Mmio`, and the rest of code is still `.write()`. <snip> >> (2) It is error prone, if you forget to call big_endian() first, it is a bug. > > Moot point when `big_endian()` is only done once. Well, you need to do it at least once per driver entry point. For DRM IOCTLs for instance you also have to consider that it is always Devres<Mmio>.
> On 5 Feb 2026, at 19:43, Danilo Krummrich <dakr@kernel.org> wrote: > > On Thu Feb 5, 2026 at 11:31 PM CET, Gary Guo wrote: >> On Thu Feb 5, 2026 at 7:05 PM GMT, Danilo Krummrich wrote: >>> (1) Devices are either little-endian or big-endian. Hence, having to write >>> >>> io.big_endian().write() >>> >>> is excessive, we always want big-endian for a big-endian device. >> >> You don't need to always write this. You just need to do `big_endian()` once >> when you obtain the io, and then keep using `BigEndian<Mmio>` instead of just >> `Mmio`, and the rest of code is still `.write()`. > > <snip> > >>> (2) It is error prone, if you forget to call big_endian() first, it is a bug. >> >> Moot point when `big_endian()` is only done once. > > Well, you need to do it at least once per driver entry point. For DRM IOCTLs for > instance you also have to consider that it is always Devres<Mmio>. > Well, this is also the case for relaxed(). I basically made peace with the fact that let mmio = mmio.relaxed(); < use mmio > is going to be a reality per driver entrypoint, unless I misunderstood? — Daniel
On Sun Feb 8, 2026 at 6:17 PM CET, Daniel Almeida wrote: > > >> On 5 Feb 2026, at 19:43, Danilo Krummrich <dakr@kernel.org> wrote: >> >> On Thu Feb 5, 2026 at 11:31 PM CET, Gary Guo wrote: >>> On Thu Feb 5, 2026 at 7:05 PM GMT, Danilo Krummrich wrote: >>>> (1) Devices are either little-endian or big-endian. Hence, having to write >>>> >>>> io.big_endian().write() >>>> >>>> is excessive, we always want big-endian for a big-endian device. >>> >>> You don't need to always write this. You just need to do `big_endian()` once >>> when you obtain the io, and then keep using `BigEndian<Mmio>` instead of just >>> `Mmio`, and the rest of code is still `.write()`. >> >> <snip> >> >>>> (2) It is error prone, if you forget to call big_endian() first, it is a bug. >>> >>> Moot point when `big_endian()` is only done once. >> >> Well, you need to do it at least once per driver entry point. For DRM IOCTLs for >> instance you also have to consider that it is always Devres<Mmio>. >> > > Well, this is also the case for relaxed(). I basically made peace with the fact that > > let mmio = mmio.relaxed(); > < use mmio > > > is going to be a reality per driver entrypoint, unless I misunderstood? Well, there are two differences: Firstly, relaxed ordering should only be possible in certain situations, but not always and not for every driver entry point. Secondly, if you mistakenly forget it, you may suffer from a pretty slight performance hit, but it is not going to be a bug.
On Thu, Feb 05, 2026 at 08:05:08PM +0100, Danilo Krummrich wrote: > On Thu Feb 5, 2026 at 6:28 PM CET, Daniel Almeida wrote: > >> On 5 Feb 2026, at 12:16, Gary Guo <gary@garyguo.net> wrote: > >> I think we should have everything default to little endian, and have wrapper > >> types that do big endian which require expicit construction, similar to > >> RelaxedMmio in Alex's series. > > > > Ah yes, the RelaxedMmio pattern is definitely a good one. I agree that we > > should head in this direction. > > I strongly disagree. > > This is a great pattern for relaxed ordering because: > > (1) We need both strict and relaxed ordering. > > (2) Relaxed ordering is rare, hence it doesn't hurt to write e.g. > > io.relaxed().write() > > (3) If you by accident just write > > io.write() > > i.e. forget to call relaxed() it s not a bug, nothing bad happens. > > Whereas for endianness it is a bad pattern because: > > (1) Devices are either little-endian or big-endian. Hence, having to write > > io.big_endian().write() > > is excessive, we always want big-endian for a big-endian device. > > (2) It is error prone, if you forget to call big_endian() first, it is a bug. > > (3) It is unergonomic in combination with relaxed ordering. > > io.big_endian().relaxed().write() > > (Does the other way around work as well? :) > > It makes much more sense to define once when we request the I/O memory whether > the device is litte-endian or big-endian. > > This could be done with different request functions, a const generic or a > function argument, but it should be done at request time. Could this ever be done in the device tree? I understand this would mean having to change all drivers and all device trees that do big endian, but it seems to be the natural location for this information. I have no idea how to structure that though. -- Link Mauve
(Cc: Rob, Saravana) On Thu Feb 5, 2026 at 10:20 PM CET, Link Mauve wrote: > On Thu, Feb 05, 2026 at 08:05:08PM +0100, Danilo Krummrich wrote: >> On Thu Feb 5, 2026 at 6:28 PM CET, Daniel Almeida wrote: >> >> On 5 Feb 2026, at 12:16, Gary Guo <gary@garyguo.net> wrote: >> >> I think we should have everything default to little endian, and have wrapper >> >> types that do big endian which require expicit construction, similar to >> >> RelaxedMmio in Alex's series. >> > >> > Ah yes, the RelaxedMmio pattern is definitely a good one. I agree that we >> > should head in this direction. >> >> I strongly disagree. >> >> This is a great pattern for relaxed ordering because: >> >> (1) We need both strict and relaxed ordering. >> >> (2) Relaxed ordering is rare, hence it doesn't hurt to write e.g. >> >> io.relaxed().write() >> >> (3) If you by accident just write >> >> io.write() >> >> i.e. forget to call relaxed() it s not a bug, nothing bad happens. >> >> Whereas for endianness it is a bad pattern because: >> >> (1) Devices are either little-endian or big-endian. Hence, having to write >> >> io.big_endian().write() >> >> is excessive, we always want big-endian for a big-endian device. >> >> (2) It is error prone, if you forget to call big_endian() first, it is a bug. >> >> (3) It is unergonomic in combination with relaxed ordering. >> >> io.big_endian().relaxed().write() >> >> (Does the other way around work as well? :) >> >> It makes much more sense to define once when we request the I/O memory whether >> the device is litte-endian or big-endian. >> >> This could be done with different request functions, a const generic or a >> function argument, but it should be done at request time. > > Could this ever be done in the device tree? I understand this would > mean having to change all drivers and all device trees that do big > endian, but it seems to be the natural location for this information. I > have no idea how to structure that though. I think that's a good idea, for newly supported devices we could probably do that. For existing ones that might not work. IIRC, there is an expectation that driver should still work with older device trees.
On Thu Feb 5, 2026 at 3:28 PM CET, Daniel Almeida wrote: > > >> On 4 Feb 2026, at 12:18, Danilo Krummrich <dakr@kernel.org> wrote: >> >> On Wed Feb 4, 2026 at 5:04 AM CET, Link Mauve wrote: >>> Another option would be to call u32::swap_bytes() on the data being >>> read/written, but these helpers make the Rust code as ergonomic as the C >>> code. >>> >>> Signed-off-by: Link Mauve <linkmauve@linkmauve.fr> >> >> The I/O stuff recently changed quite significantly, please have a look at the >> driver-core-next branch [1] in the driver-core tree. >> >> Also, instead of providing additional *be() methods, we should just create a new >> type io::Endianness and use it to indicate the device endianness when requesting >> the I/O resource. >> >> For instance, for your driver we could have >> >> request.iomap_exclusive_sized::<8>(Endianness::Big)? > > Can we please structure this in a way that LittleEndian is the default? > Perhaps using a const generic that is defaulted, or something along these lines. Yes, little-endian should absolutely be the default. Please don't take the above as specific suggestion. :) >> >> and then let the I/O backend choose the correct accessors based on this. >> >> I.e. the device is either big or little endian, hence we don't need to provide >> both accessors at the same time. >> >> [1] https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/log/?h=driver-core-next >>
On Wed Feb 4, 2026 at 4:18 PM CET, Danilo Krummrich wrote: > On Wed Feb 4, 2026 at 5:04 AM CET, Link Mauve wrote: >> Another option would be to call u32::swap_bytes() on the data being >> read/written, but these helpers make the Rust code as ergonomic as the C >> code. >> >> Signed-off-by: Link Mauve <linkmauve@linkmauve.fr> > > The I/O stuff recently changed quite significantly, please have a look at the > driver-core-next branch [1] in the driver-core tree. > > Also, instead of providing additional *be() methods, we should just create a new > type io::Endianness and use it to indicate the device endianness when requesting > the I/O resource. > > For instance, for your driver we could have > > request.iomap_exclusive_sized::<8>(Endianness::Big)? > > and then let the I/O backend choose the correct accessors based on this. > > I.e. the device is either big or little endian, hence we don't need to provide > both accessors at the same time. > > [1] https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/log/?h=driver-core-next Forgot to mention, please also consider this patch series [2] for your work. [2] https://lore.kernel.org/all/20260202-io-v1-0-9bb2177d23be@nvidia.com/
© 2016 - 2026 Red Hat, Inc.