* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: Theodore Ts'o @ 2025-11-11 3:51 UTC (permalink / raw)
To: H. Peter Anvin
Cc: Maarten Brock, linux-serial@vger.kernel.org,
linux-api@vger.kernel.org, LKML
In-Reply-To: <0F8021E8-F288-4669-8195-9948844E36FD@zytor.com>
On Mon, Nov 10, 2025 at 01:05:55PM -0800, H. Peter Anvin wrote:
>
> The parport driver layer is kind of similar to this, in some ways,
> but in the tty layer that is mostly handled by line disciplines
> instead. (The parport hardware was generally abused on a much lower
> level, as a substitute for GPIOs, so even the notion of a byte
> stream wasn't there.)
I'm not an RS-485 expert, but over the years, I've heard all sorts of
"interesting" timing requirements. For example RTS can only be
dropped when the UART's shift register has completely drained. That's
not when the OS has sent the characters to the FIFO (which is why
tcdrain isn't quite what you want); it's not when the UART has sent
the transmit interrupt, but when the UART's shift register is *empty*.
Doing this via the termios interface is decidedly non-optimal[1].
[1] https://www.moxa.com.cn/getmedia/a07808dd-f3d7-473b-9a2f-93b5ce673bb1/moxa-the-secrets-of-rs-485-half-duplex-communication-tech-note-v1.0.pdf
It *can* be done, sure, with some strategic usleep()'s, but not
necessarily optimally, and it is decidedly hacky. From what I can
tell, it's actually not *that* different from treating RTS as a GPIO
pin, and really, this ought to be done in the kernel if you want
optimal control oer the timing of when RTS gets toggeled.
> *If* I'm reading the code correctly – which is a little complicated due to the sheer number of abstraction layers – hardware initialization is already deferred until first open, which would mean that disabling autoconfiguration (one of the features in TIOCSSERIAL) would again be a valid reason for wanting to be able to communicate with a device driver before requiring that it puts the underlying hardware in the state expected for operation *in the mode configured* (catch-22).
Well, part of the problem is that the PC's serial port predates PCI,
so there's no way we can tell whether or not there is a serial port at
a particular I/O port except by poking certain I/O ports, and seeing
if there is something that looks like a UART which responds.
Hopefully this won't accidentaly cause the NSA's thermite-charge
self-destruct charge from getting set off, and in practice, mainboard
designers who try to put things at the COM1/2/3/4 generally get weeded
out thanks to natural selection, hopefully without harming users along
the way. :-)
Worse, we also wanted to support serial cards that were at
non-standard ports, or using non-standard interrupts, and so that's
one of the reason hardware initialization is deferred until after we
have a chance to configure the serial device's I/O port and interrupt.
Now, we are in the 21st century, and just as we are trying to declare
32-bit i386 dead (to the disappointment of legacy hardware owners
everywhere who are whining about Debian dropping installer support for
i386), we could try to declare the ISA bus as dead, and no longer
being supported, and we could probably drop a bunch of this
complexity. We probably would need to support COM 1/2/3/4 ports since
these still tend to be hardwared without a way of detecting it their
existing via USB or PCI bus discovery mechanisms. And for those, we
would still need to do UART autoconfiguration. I suppose we could
just assume that all UART's are 16550A's, and that noone would
actually use older UART's type --- except (a) I bet there are some
cheap-skate embedded computer designers who decided to use a 8250
instead of 16550A for $REASONS$, and because of the extreme timing
requirements of some RS-485 use cases, I believe I have seen
recommendations to use setserial to actually force the UART into 8250
mode, to disable the TX FIFO --- all to satisfying the weird-shit RTS
timing requirements. (This is where I get my beief that RS-485 !=
RS-232, and if you want to do all of this weird-shit timing
requirements because you are trying to implemet a multi-node bus which
is half-duplex, maybe this should be done in the kernel, damn it!)
How about this? If you really don't want to open the device to
configure it not to assert DTR/RTS, perhaps we could specify on the
boot command line that one of TTYS[0123] has a different default line
discipline, and when tty's are opened when the RS-485 line displine,
the RS-485 rules apply, which would include not messing with DTR/RTS,
and if you want to play games with RTS getting dropped as soon as the
last bit has let the transmit shift register, we can get the kernel do
this in the line discpline, which arguably would be *way* more
reliable than playing GPIO-style timing games in userspace.
- Ted
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: H. Peter Anvin @ 2025-11-10 21:05 UTC (permalink / raw)
To: Theodore Ts'o, Maarten Brock
Cc: linux-serial@vger.kernel.org, linux-api@vger.kernel.org, LKML
In-Reply-To: <20251110201933.GH2988753@mit.edu>
On November 10, 2025 12:19:33 PM PST, Theodore Ts'o <tytso@mit.edu> wrote:
>On Mon, Nov 10, 2025 at 10:06:02AM +0000, Maarten Brock wrote:
>> I fully agree that you cannot expect users that wired something like RS485 Driver
>> Enable or a microcontroller reset to RTS or DTR to write their own kernel driver.
>> And you need to open the port to make the appropriate settings. But opening a
>> port should not e.g. claim the RS485 bus and mess up whatever communication
>> was going on there.
>
>Again, the existing seral driver code *will* mess with RTS and DTR at
>boot up because that's part of the autoconfiuration code, and that was
>added because it was needed for some number of serial ports.
>
>If that's going to "mess up" the RS485 bus, maybe we need accept that
>RS-232 != RS-485 and have a different driver for the two. That's
>going to be a lot simpler than trying to make the same code work for
>both RS-232 and RS-485, and claiming that the existing RS-232 code is
>"fundamentally buggy" when it interacts poorly with something that has
>very different requirements than the historical RS-232 use cases.
>
> - Ted
I didn't say it was fundamentally buggy; if I did or implied it I apologize. It is most definitely not; however, in some cases it is undesired or undesirable *due to shifts in usage patterns.*
This isn't a bug at all but an enormous strength. That a 65-year-old standard — both hardware and software — designed for teletypes and dumb terminals can still be useful today is almost the definition of success. And, yes, some glitches in that process are going to be inevitable — like the mistake of retaining the termio Bxxx emumeration constants in the termios interface. But we deal with it by gradual evolution of interfaces.
One such example is RTS itself: the RS485 definition is, in fact, the originally intended meaning of RTS: it is a request to the DCE to negotiate transmission privilege and activate transmission mode over a half duplex channel, after which it asserts CTS. RTS/CTS flow control was a nonstandard adaption to allow for binary transparent flow control over full duplex links — it wasn't formally standardized until 1991, and the signal is formally named RTR when used that way. However, RTR and RTS share hardware in nearly all existing implementations, and share pins in the standard — so whether or not you are using RTR or RTS is a property of the DCE, not DTE, and needs to be configured into the DTE.
Requiring new drivers for the gajillion different hardware devices already supported and then having the problem of which drivers claim it isn't really any better of a solution; one could in fact argue it is *exactly* equivalent to being able to indicate to the driver what mode one wants it to operate in before it does its configuration.
The parport driver layer is kind of similar to this, in some ways, but in the tty layer that is mostly handled by line disciplines instead. (The parport hardware was generally abused on a much lower level, as a substitute for GPIOs, so even the notion of a byte stream wasn't there.)
*If* I'm reading the code correctly – which is a little complicated due to the sheer number of abstraction layers – hardware initialization is already deferred until first open, which would mean that disabling autoconfiguration (one of the features in TIOCSSERIAL) would again be a valid reason for wanting to be able to communicate with a device driver before requiring that it puts the underlying hardware in the state expected for operation *in the mode configured* (catch-22).
As I stated, this is inherently going to be a best effort. For some devices that may mean simply leaving the power on default in place (in this specific case, presumably, DTR# and RTS# deasserted.) However, once the state is already known to the kernel then there is no such issue for any hardware.
As far as naming is concerned: O_RAW is really suboptimal, as it has exactly the same implications as O_DIRECT (I/O goes straight to the device with no buffering.) I don't like the idea of abusing O_DIRECT at all; I only brought it up as a fallback alternative. I genuinely do believe that if we assign a new open flag it will find use cases outside the tty/serial port subsystems, and if there is anything Unix has done right it is to generalize interfaces as much as possible, case in point descriptors.
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: Theodore Ts'o @ 2025-11-10 20:19 UTC (permalink / raw)
To: Maarten Brock
Cc: H. Peter Anvin, linux-serial@vger.kernel.org,
linux-api@vger.kernel.org, LKML
In-Reply-To: <AMBPR05MB11925DA076098B05E418BF64283CEA@AMBPR05MB11925.eurprd05.prod.outlook.com>
On Mon, Nov 10, 2025 at 10:06:02AM +0000, Maarten Brock wrote:
> I fully agree that you cannot expect users that wired something like RS485 Driver
> Enable or a microcontroller reset to RTS or DTR to write their own kernel driver.
> And you need to open the port to make the appropriate settings. But opening a
> port should not e.g. claim the RS485 bus and mess up whatever communication
> was going on there.
Again, the existing seral driver code *will* mess with RTS and DTR at
boot up because that's part of the autoconfiuration code, and that was
added because it was needed for some number of serial ports.
If that's going to "mess up" the RS485 bus, maybe we need accept that
RS-232 != RS-485 and have a different driver for the two. That's
going to be a lot simpler than trying to make the same code work for
both RS-232 and RS-485, and claiming that the existing RS-232 code is
"fundamentally buggy" when it interacts poorly with something that has
very different requirements than the historical RS-232 use cases.
- Ted
^ permalink raw reply
* Re: [PATCH v5 08/22] liveupdate: luo_file: implement file systems callbacks
From: Pasha Tatashin @ 2025-11-10 17:42 UTC (permalink / raw)
To: Pratyush Yadav
Cc: jasonmiu, graf, rppt, dmatlack, rientjes, corbet, rdunlap,
ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, akpm, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, lennart,
brauner, linux-api, linux-fsdevel, saeedm, ajayachandra, jgg,
parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <mafs0ms4tajcs.fsf@kernel.org>
On Mon, Nov 10, 2025 at 12:27 PM Pratyush Yadav <pratyush@kernel.org> wrote:
>
> Hi Pasha,
>
> Caught a small bug during some of my testing.
>
> On Fri, Nov 07 2025, Pasha Tatashin wrote:
>
> > This patch implements the core mechanism for managing preserved
> > files throughout the live update lifecycle. It provides the logic to
> > invoke the file handler callbacks (preserve, unpreserve, freeze,
> > unfreeze, retrieve, and finish) at the appropriate stages.
> >
> > During the reboot phase, luo_file_freeze() serializes the final
> > metadata for each file (handler compatible string, token, and data
> > handle) into a memory region preserved by KHO. In the new kernel,
> > luo_file_deserialize() reconstructs the in-memory file list from this
> > data, preparing the session for retrieval.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> [...]
> > +int luo_preserve_file(struct luo_session *session, u64 token, int fd)
> > +{
> > + struct liveupdate_file_op_args args = {0};
> > + struct liveupdate_file_handler *fh;
> > + struct luo_file *luo_file;
> > + struct file *file;
> > + int err = -ENOENT;
> > +
> > + lockdep_assert_held(&session->mutex);
> > +
> > + if (luo_token_is_used(session, token))
> > + return -EEXIST;
> > +
> > + file = fget(fd);
> > + if (!file)
> > + return -EBADF;
> > +
> > + err = luo_session_alloc_files_mem(session);
>
> err gets set to 0 here...
>
> > + if (err)
> > + goto exit_err;
> > +
> > + if (session->count == LUO_FILE_MAX) {
> > + err = -ENOSPC;
> > + goto exit_err;
> > + }
> > +
> > + list_for_each_entry(fh, &luo_file_handler_list, list) {
> > + if (fh->ops->can_preserve(fh, file)) {
> > + err = 0;
> > + break;
> > + }
> > + }
>
> ... say no file handler can preserve this file ...
>
> > +
> > + /* err is still -ENOENT if no handler was found */
> > + if (err)
>
> ... err is not ENOENT, but 0. So this function does not error but, but
> goes ahead with fh == luo_file_handler_list (since end of list). This
> causes an out-of-bounds access. It eventually causes a kernel fault and
> panic.
>
> You should drop the ENOENT at initialization time and set it right
> before list_for_each_entry().
Right, thank you for reporting this. Should add it to self-tests,
where we try to preserve FD that does not have a file handler.
Pasha
>
> > + goto exit_err;
> > +
> > + luo_file = kzalloc(sizeof(*luo_file), GFP_KERNEL);
> > + if (!luo_file) {
> > + err = -ENOMEM;
> > + goto exit_err;
> > + }
> > +
> > + luo_file->file = file;
> > + luo_file->fh = fh;
> > + luo_file->token = token;
> > + luo_file->retrieved = false;
> > + mutex_init(&luo_file->mutex);
> > +
> > + args.handler = fh;
> > + args.session = (struct liveupdate_session *)session;
> > + args.file = file;
> > + err = fh->ops->preserve(&args);
> > + if (err) {
> > + mutex_destroy(&luo_file->mutex);
> > + kfree(luo_file);
> > + goto exit_err;
> > + } else {
> > + luo_file->serialized_data = args.serialized_data;
> > + list_add_tail(&luo_file->list, &session->files_list);
> > + session->count++;
> > + }
> > +
> > + return 0;
> > +
> > +exit_err:
> > + fput(file);
> > + luo_session_free_files_mem(session);
> > +
> > + return err;
> > +}
> [...]
>
> --
> Regards,
> Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v5 08/22] liveupdate: luo_file: implement file systems callbacks
From: Pratyush Yadav @ 2025-11-10 17:27 UTC (permalink / raw)
To: Pasha Tatashin
Cc: pratyush, jasonmiu, graf, rppt, dmatlack, rientjes, corbet,
rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, akpm,
tj, yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, lennart,
brauner, linux-api, linux-fsdevel, saeedm, ajayachandra, jgg,
parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-9-pasha.tatashin@soleen.com>
Hi Pasha,
Caught a small bug during some of my testing.
On Fri, Nov 07 2025, Pasha Tatashin wrote:
> This patch implements the core mechanism for managing preserved
> files throughout the live update lifecycle. It provides the logic to
> invoke the file handler callbacks (preserve, unpreserve, freeze,
> unfreeze, retrieve, and finish) at the appropriate stages.
>
> During the reboot phase, luo_file_freeze() serializes the final
> metadata for each file (handler compatible string, token, and data
> handle) into a memory region preserved by KHO. In the new kernel,
> luo_file_deserialize() reconstructs the in-memory file list from this
> data, preparing the session for retrieval.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
[...]
> +int luo_preserve_file(struct luo_session *session, u64 token, int fd)
> +{
> + struct liveupdate_file_op_args args = {0};
> + struct liveupdate_file_handler *fh;
> + struct luo_file *luo_file;
> + struct file *file;
> + int err = -ENOENT;
> +
> + lockdep_assert_held(&session->mutex);
> +
> + if (luo_token_is_used(session, token))
> + return -EEXIST;
> +
> + file = fget(fd);
> + if (!file)
> + return -EBADF;
> +
> + err = luo_session_alloc_files_mem(session);
err gets set to 0 here...
> + if (err)
> + goto exit_err;
> +
> + if (session->count == LUO_FILE_MAX) {
> + err = -ENOSPC;
> + goto exit_err;
> + }
> +
> + list_for_each_entry(fh, &luo_file_handler_list, list) {
> + if (fh->ops->can_preserve(fh, file)) {
> + err = 0;
> + break;
> + }
> + }
... say no file handler can preserve this file ...
> +
> + /* err is still -ENOENT if no handler was found */
> + if (err)
... err is not ENOENT, but 0. So this function does not error but, but
goes ahead with fh == luo_file_handler_list (since end of list). This
causes an out-of-bounds access. It eventually causes a kernel fault and
panic.
You should drop the ENOENT at initialization time and set it right
before list_for_each_entry().
> + goto exit_err;
> +
> + luo_file = kzalloc(sizeof(*luo_file), GFP_KERNEL);
> + if (!luo_file) {
> + err = -ENOMEM;
> + goto exit_err;
> + }
> +
> + luo_file->file = file;
> + luo_file->fh = fh;
> + luo_file->token = token;
> + luo_file->retrieved = false;
> + mutex_init(&luo_file->mutex);
> +
> + args.handler = fh;
> + args.session = (struct liveupdate_session *)session;
> + args.file = file;
> + err = fh->ops->preserve(&args);
> + if (err) {
> + mutex_destroy(&luo_file->mutex);
> + kfree(luo_file);
> + goto exit_err;
> + } else {
> + luo_file->serialized_data = args.serialized_data;
> + list_add_tail(&luo_file->list, &session->files_list);
> + session->count++;
> + }
> +
> + return 0;
> +
> +exit_err:
> + fput(file);
> + luo_session_free_files_mem(session);
> +
> + return err;
> +}
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v5 02/22] liveupdate: luo_core: integrate with KHO
From: Pasha Tatashin @ 2025-11-10 15:43 UTC (permalink / raw)
To: Mike Rapoport
Cc: pratyush, jasonmiu, graf, dmatlack, rientjes, corbet, rdunlap,
ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, akpm, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <aRHiCxoJnEGmj17q@kernel.org>
>
> kho_finalize() should be really called from kernel_kexec().
>
> We avoided it because of the concern that memory allocations that late in
> reboot could be an issue. But I looked at hibernate() and it does
> allocations on reboot->hibernate path, so adding kho_finalize() as the
> first step of kernel_kexec() seems fine.
This isn't a regular reboot; it's a live update. The
liveupdate_reboot() is designed to be reversible and allows us to
return an error, undoing the freeze() operations via unfreeze() in
case of failure.
This is why this call is placed first in reboot(), before any
irreversible reboot notifiers or shutdown callbacks are performed. If
an allocation problem occurs in KHO, the error is simply reported back
to userspace, and the live update update is safely aborted.
> And if we prioritize stateless memory tracking in KHO, it won't be a
> concern at all.
We are prioritizing stateless KHO work ;-) +Jason Miu
Once KHO is stateless, the kho_finalize() is going to be removed.
>
> > + if (err) {
> > + pr_err("kho_finalize failed %d\n", err);
> > + /*
> > + * kho_finalize() may return libfdt errors, to aboid passing to
> > + * userspace unknown errors, change this to EAGAIN.
> > + */
> > + err = -EAGAIN;
> > + }
> > +
> > + return err;
> > }
> >
> > /**
>
> --
> Sincerely yours,
> Mike.
^ permalink raw reply
* Re: [PATCH v5 05/22] liveupdate: kho: when live update add KHO image during kexec load
From: Pasha Tatashin @ 2025-11-10 15:31 UTC (permalink / raw)
To: Mike Rapoport
Cc: pratyush, jasonmiu, graf, dmatlack, rientjes, corbet, rdunlap,
ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, akpm, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <aRHe3syRvOrCYC9u@kernel.org>
On Mon, Nov 10, 2025 at 7:47 AM Mike Rapoport <rppt@kernel.org> wrote:
>
> On Fri, Nov 07, 2025 at 04:03:03PM -0500, Pasha Tatashin wrote:
> > In case KHO is driven from within kernel via live update, finalize will
> > always happen during reboot, so add the KHO image unconditionally.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> > ---
> > kernel/liveupdate/kexec_handover.c | 3 ++-
> > 1 file changed, 2 insertions(+), 1 deletion(-)
> >
> > diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> > index 9f0913e101be..b54ca665e005 100644
> > --- a/kernel/liveupdate/kexec_handover.c
> > +++ b/kernel/liveupdate/kexec_handover.c
> > @@ -15,6 +15,7 @@
> > #include <linux/kexec_handover.h>
> > #include <linux/libfdt.h>
> > #include <linux/list.h>
> > +#include <linux/liveupdate.h>
> > #include <linux/memblock.h>
> > #include <linux/page-isolation.h>
> > #include <linux/vmalloc.h>
> > @@ -1489,7 +1490,7 @@ int kho_fill_kimage(struct kimage *image)
> > int err = 0;
> > struct kexec_buf scratch;
> >
> > - if (!kho_out.finalized)
> > + if (!kho_out.finalized && !liveupdate_enabled())
> > return 0;
>
> This feels backwards, I don't think KHO should call liveupdate methods.
It is backward, but it is a requirement until KHO becomes stateless.
LUO does not have dependencies on userspace state of when kexec is
loaded. In fact the next kernel must be loaded before the brownout as
it is an expensive operation. The sequence of events should:
1. Load the next kernel in memory
2. Preserve resources via LUO
3. Do Kexec reboot
Pasha
^ permalink raw reply
* Re: [PATCH v5 02/22] liveupdate: luo_core: integrate with KHO
From: Mike Rapoport @ 2025-11-10 13:00 UTC (permalink / raw)
To: Pasha Tatashin
Cc: pratyush, jasonmiu, graf, dmatlack, rientjes, corbet, rdunlap,
ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, akpm, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-3-pasha.tatashin@soleen.com>
On Fri, Nov 07, 2025 at 04:03:00PM -0500, Pasha Tatashin wrote:
> Integrate the LUO with the KHO framework to enable passing LUO state
> across a kexec reboot.
>
> When LUO is transitioned to a "prepared" state, it tells KHO to
> finalize, so all memory segments that were added to KHO preservation
> list are getting preserved. After "Prepared" state no new segments
> can be preserved. If LUO is canceled, it also tells KHO to cancel the
> serialization, and therefore, later LUO can go back into the prepared
> state.
>
> This patch introduces the following changes:
> - During the KHO finalization phase allocate FDT blob.
> - Populate this FDT with a LUO compatibility string ("luo-v1").
>
> LUO now depends on `CONFIG_KEXEC_HANDOVER`. The core state transition
> logic (`luo_do_*_calls`) remains unimplemented in this patch.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
...
> @@ -69,7 +197,22 @@ early_param("liveupdate", early_liveupdate_param);
> */
> int liveupdate_reboot(void)
> {
> - return 0;
> + int err;
> +
> + if (!liveupdate_enabled())
> + return 0;
> +
> + err = kho_finalize();
kho_finalize() should be really called from kernel_kexec().
We avoided it because of the concern that memory allocations that late in
reboot could be an issue. But I looked at hibernate() and it does
allocations on reboot->hibernate path, so adding kho_finalize() as the
first step of kernel_kexec() seems fine.
And if we prioritize stateless memory tracking in KHO, it won't be a
concern at all.
> + if (err) {
> + pr_err("kho_finalize failed %d\n", err);
> + /*
> + * kho_finalize() may return libfdt errors, to aboid passing to
> + * userspace unknown errors, change this to EAGAIN.
> + */
> + err = -EAGAIN;
> + }
> +
> + return err;
> }
>
> /**
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH v5 05/22] liveupdate: kho: when live update add KHO image during kexec load
From: Mike Rapoport @ 2025-11-10 12:47 UTC (permalink / raw)
To: Pasha Tatashin
Cc: pratyush, jasonmiu, graf, dmatlack, rientjes, corbet, rdunlap,
ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, akpm, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-6-pasha.tatashin@soleen.com>
On Fri, Nov 07, 2025 at 04:03:03PM -0500, Pasha Tatashin wrote:
> In case KHO is driven from within kernel via live update, finalize will
> always happen during reboot, so add the KHO image unconditionally.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
> kernel/liveupdate/kexec_handover.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 9f0913e101be..b54ca665e005 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -15,6 +15,7 @@
> #include <linux/kexec_handover.h>
> #include <linux/libfdt.h>
> #include <linux/list.h>
> +#include <linux/liveupdate.h>
> #include <linux/memblock.h>
> #include <linux/page-isolation.h>
> #include <linux/vmalloc.h>
> @@ -1489,7 +1490,7 @@ int kho_fill_kimage(struct kimage *image)
> int err = 0;
> struct kexec_buf scratch;
>
> - if (!kho_out.finalized)
> + if (!kho_out.finalized && !liveupdate_enabled())
> return 0;
This feels backwards, I don't think KHO should call liveupdate methods.
> image->kho.fdt = virt_to_phys(kho_out.fdt);
> --
> 2.51.2.1041.gc1ab5b90ca-goog
>
--
Sincerely yours,
Mike.
^ permalink raw reply
* RE: RFC: Serial port DTR/RTS - O_NRESETDEV
From: Maarten Brock @ 2025-11-10 10:06 UTC (permalink / raw)
To: H. Peter Anvin, Theodore Ts'o
Cc: linux-serial@vger.kernel.org, linux-api@vger.kernel.org, LKML
In-Reply-To: <ADB50E23-DC8B-43D0-A345-E10396A3DFD4@zytor.com>
> -----Original Message-----
> From: H. Peter Anvin <hpa@zytor.com>
> Sent: Monday, 10 November 2025 06:00
> To: Theodore Ts'o <tytso@mit.edu>
> Cc: linux-serial@vger.kernel.org; linux-api@vger.kernel.org; LKML <linux-
> kernel@vger.kernel.org>
> Subject: Re: RFC: Serial port DTR/RTS - O_NRESETDEV
>
> On November 9, 2025 7:35:56 PM PST, Theodore Ts'o <tytso@mit.edu> wrote:
> >On Sat, Nov 08, 2025 at 06:25:20PM -0800, H. Peter Anvin wrote:
> >>
> >> The standard ESP32 configuration for its serial port is that asserting RTS#
> >> even for a moment will cause a device reset, and asserting DTR# during reset
> >> forces the device into boot mode. So even if you execute TIOCMSET
> immediately
> >> after opening the device, you will have glitched the output, and only the
> >> capacitance of the output will save you, in the best case.
> >
> >IMHO, these more esoteric use cases should involve a custom kernel
> >driver which replaces the generic serial driver. In practice, these
> >things aren't really a tty, but somethiung else weird, and trying to
> >do this in userspace seems really awkward.
> >
> >> setserial (TIOCSSERIAL) and termios (TCSETS*) both require file descriptors,
> >> so that is not suitable. The 8250 driver, but *not* other serial drivers,
> >> allows the setserial information to be accessed via sysfs; however, this
> >> functionality is local to the 8250 driver.
> >
> >My suggestion of using setserial to turn on some "not really a tty;
> >but some weird networking / cheap debugging hack" flag should work,
> >because you would do this at boot up. Note that the 8250
> >autoconfiguration code (see drivers/tty/serial/8250/8250_port.c) is
> >going to mess with DTR / RTS. This is why I asserted that trying to
> >claim that you can preserve "state" across reboots is Just Not
> >Possible.
> >
> >If you have some weird setup where DTR or RTS is wierd to the
> >"detonate the TNT" line, might I suggest that maybe we shouldn't be
> >using the tty / 8250 serial driver, but it should ***really*** be a
> >dedicated kernel driver?
> >
> > - Ted
>
> That is a completely unrealistic idea. And you are hardly the first one to have it.
> Microsoft has been trying to get rid of serial and parallel ports since the 1990s for
> reasons like this.
>
> Microsoft even have had to back off the requirement of having .ini text file
> "drivers" for ACM serial ports
>
> Yet they probably will still be with us when the 22nd century dawns, exactly
> *because* they are ubiquitous, supported by everything, and require no separate
> kernel drivers.
>
> And these days these aren't the "esoteric" use cases at all. They are the norm.
I fully agree that you cannot expect users that wired something like RS485 Driver
Enable or a microcontroller reset to RTS or DTR to write their own kernel driver.
And you need to open the port to make the appropriate settings. But opening a
port should not e.g. claim the RS485 bus and mess up whatever communication
was going on there.
Kind regards,
Maarten Brock
^ permalink raw reply
* Re: truncatat? was, Re: [RFC] xfs: fake fallocate success for always CoW inodes
From: Florian Weimer @ 2025-11-10 10:00 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Matthew Wilcox, Hans Holmberg, linux-xfs, Carlos Maiolino,
Dave Chinner, Darrick J . Wong, linux-fsdevel, linux-kernel,
linux-api, libc-alpha
In-Reply-To: <20251110094829.GA24081@lst.de>
* Christoph Hellwig:
> On Mon, Nov 10, 2025 at 10:31:40AM +0100, Christoph Hellwig wrote:
>> fallocate seems like an odd interface choice for that, but given that
>> (f)truncate doesn't have a flags argument that might still be the
>> least unexpected version.
>>
>> > Maybe add two flags, one for the ftruncate replacement, and one that
>> > instructs the file system that the range will be used with mmap soon?
>> > I expect this could be useful information to the file system. We
>> > wouldn't use it in posix_fallocate, but applications calling fallocate
>> > directly might.
>>
>> What do you think "to be used with mmap" flag could be useful for
>> in the file system? For file systems mmap I/O isn't very different
>> from other use cases.
>
> The usual way to pass extra flags was the flats at for the *at syscalls.
> truncate doesn't have that, and I wonder if there would be uses for
> that? Because if so that feels like the right way to add that feature.
> OTOH a quick internet search only pointed to a single question about it,
> which was related to other confusion in the use of (f)truncate.
>
> While adding a new system call can be rather cumbersome, the advantage
> would be that we could implement the "only increase file size" flag
> in common code and it would work on all file systems for kernels that
> support the system call.
There are some references to ftruncateat:
<https://codesearch.debian.net/search?q=ftruncateat&literal=1>
I don't have a particularly strong opinion on the choice of interface.
I can't find anything in the Austin Group tracker that suggests that
they are considering standardizing ftruncateat without a flags argument.
Thanks,
Florian
^ permalink raw reply
* truncatat? was, Re: [RFC] xfs: fake fallocate success for always CoW inodes
From: Christoph Hellwig @ 2025-11-10 9:48 UTC (permalink / raw)
To: Florian Weimer
Cc: Christoph Hellwig, Florian Weimer, Matthew Wilcox, Hans Holmberg,
linux-xfs, Carlos Maiolino, Dave Chinner, Darrick J . Wong,
linux-fsdevel, linux-kernel, linux-api, libc-alpha
In-Reply-To: <20251110093140.GA22674@lst.de>
On Mon, Nov 10, 2025 at 10:31:40AM +0100, Christoph Hellwig wrote:
> fallocate seems like an odd interface choice for that, but given that
> (f)truncate doesn't have a flags argument that might still be the
> least unexpected version.
>
> > Maybe add two flags, one for the ftruncate replacement, and one that
> > instructs the file system that the range will be used with mmap soon?
> > I expect this could be useful information to the file system. We
> > wouldn't use it in posix_fallocate, but applications calling fallocate
> > directly might.
>
> What do you think "to be used with mmap" flag could be useful for
> in the file system? For file systems mmap I/O isn't very different
> from other use cases.
The usual way to pass extra flags was the flats at for the *at syscalls.
truncate doesn't have that, and I wonder if there would be uses for
that? Because if so that feels like the right way to add that feature.
OTOH a quick internet search only pointed to a single question about it,
which was related to other confusion in the use of (f)truncate.
While adding a new system call can be rather cumbersome, the advantage
would be that we could implement the "only increase file size" flag
in common code and it would work on all file systems for kernels that
support the system call.
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: H. Peter Anvin @ 2025-11-10 5:20 UTC (permalink / raw)
To: Theodore Ts'o; +Cc: linux-serial, linux-api, LKML
In-Reply-To: <20251107173743.GA3131573@mit.edu>
On November 7, 2025 9:37:43 AM PST, Theodore Ts'o <tytso@mit.edu> wrote:
>On Thu, Nov 06, 2025 at 11:53:23PM -0800, H. Peter Anvin wrote:
>>
>> I recently ran into a pretty serious issue due to the Unix/Linux
>> (mis)behavior of forcing DTR and RTS asserted when a serial port is
>> set, losing the pre-existing status in the process.
>
>There's a hidden assumption in your problem statement which is that
>DTR / RTS has a "state" which can be saved when the serial port is not
>active, where active is one or more file descriptors holding the
>serial port open. There may be certain hardware or drivers where this
>is just not possible, because nothing is defined if the serial port is
>not active. It might make sense if you are using a 8250 UART, but not
>all the world is the National Semiconductor (or clones) UART.
>
>Certainly the "state" will not be preserved across boots, since how we
>autodetect the UART is going to mess with UART settings. So
>*presumably* what you are talking about is you want to be able to open
>the serial port, mess with DTR / RTS, and then be able to close the
>serial port, and then later on, re-open the serial port, have the DTR
>/ RTS remain the same. And it's Too Hard(tm) to have userspace
>keeping a file descriptor open during the whole time? (Which is
>traditionally how Unix/Linux has required that applications do
>things.)
>
>Is that a fair summary of the requirements?
>
>> It seems to me that this may very well be a problem beyond ttys, in
>> which case a new open flag to request to a driver that the
>> configuration and (observable) state of the underlying hardware
>> device -- whatever it may be -- should not be disturbed by calling
>> open(). This is of course already the case for many devices, not to
>> mention block and non-devices, in which case this flag is a don't
>> care.
>
>I think it's going to be a lot simpler to keep this specific to serial
>ports and DTR / RTS, because the concept that the hardware should not
>be changed when the file descriptor is opened may simply not be
>possible. For example, it might be that until you open it, the there
>might not even be power applied to the device. The concept that all
>hardware should burn battery power once the machine is booted may not
>make sense, and the assumption that hardware has the extra
>millicent(s) worth of silicon to maintain state when power is dropped
>may again, not be something that we can assume as being possible for
>all devices.
>
>If that's the case, if you want to have something where DTR and RTS
>stay the same, and for some reason we can't assume that userspace
>can't just keep a process holding the tty device open, my suggestion is to use
>
>Given that DTR and RTS are secial port concepts, my suggesiton is to
>set a serial port flag, using setserial(8). It may be the case that
>for certain types of serial device, the attempt to set the flag may be
>rejected, but that's something which the ioctl used by setserial
>already can do and which userspace applications such as setserial
>understand may be the case.
>
>Cheers,
>
> - Ted
So let's separate out a few things here:
1. You are taking about using setserial(8), which is really ioctl(TIOCSSERIAL), which requires a file descriptor. This is exactly why I believe there should be a mechanism for acquiring a file descriptor which *by that action itself* should not change whatever state is already available to the kernel.
2. What, if anything, can be done on a device by device basis to improve the situation beyond what currently exists.
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: H. Peter Anvin @ 2025-11-10 5:00 UTC (permalink / raw)
To: Theodore Ts'o; +Cc: linux-serial, linux-api, LKML
In-Reply-To: <20251110033556.GC2988753@mit.edu>
On November 9, 2025 7:35:56 PM PST, Theodore Ts'o <tytso@mit.edu> wrote:
>On Sat, Nov 08, 2025 at 06:25:20PM -0800, H. Peter Anvin wrote:
>>
>> The standard ESP32 configuration for its serial port is that asserting RTS#
>> even for a moment will cause a device reset, and asserting DTR# during reset
>> forces the device into boot mode. So even if you execute TIOCMSET immediately
>> after opening the device, you will have glitched the output, and only the
>> capacitance of the output will save you, in the best case.
>
>IMHO, these more esoteric use cases should involve a custom kernel
>driver which replaces the generic serial driver. In practice, these
>things aren't really a tty, but somethiung else weird, and trying to
>do this in userspace seems really awkward.
>
>> setserial (TIOCSSERIAL) and termios (TCSETS*) both require file descriptors,
>> so that is not suitable. The 8250 driver, but *not* other serial drivers,
>> allows the setserial information to be accessed via sysfs; however, this
>> functionality is local to the 8250 driver.
>
>My suggestion of using setserial to turn on some "not really a tty;
>but some weird networking / cheap debugging hack" flag should work,
>because you would do this at boot up. Note that the 8250
>autoconfiguration code (see drivers/tty/serial/8250/8250_port.c) is
>going to mess with DTR / RTS. This is why I asserted that trying to
>claim that you can preserve "state" across reboots is Just Not
>Possible.
>
>If you have some weird setup where DTR or RTS is wierd to the
>"detonate the TNT" line, might I suggest that maybe we shouldn't be
>using the tty / 8250 serial driver, but it should ***really*** be a
>dedicated kernel driver?
>
> - Ted
That is a completely unrealistic idea. And you are hardly the first one to have it. Microsoft has been trying to get rid of serial and parallel ports since the 1990s for reasons like this.
Microsoft even have had to back off the requirement of having .ini text file "drivers" for ACM serial ports
Yet they probably will still be with us when the 22nd century dawns, exactly *because* they are ubiquitous, supported by everything, and require no separate kernel drivers.
And these days these aren't the "esoteric" use cases at all. They are the norm.
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: Theodore Ts'o @ 2025-11-10 3:35 UTC (permalink / raw)
To: H. Peter Anvin; +Cc: linux-serial, linux-api, LKML
In-Reply-To: <dc42f5d4-a707-4442-bda6-1c1990666f54@zytor.com>
On Sat, Nov 08, 2025 at 06:25:20PM -0800, H. Peter Anvin wrote:
>
> The standard ESP32 configuration for its serial port is that asserting RTS#
> even for a moment will cause a device reset, and asserting DTR# during reset
> forces the device into boot mode. So even if you execute TIOCMSET immediately
> after opening the device, you will have glitched the output, and only the
> capacitance of the output will save you, in the best case.
IMHO, these more esoteric use cases should involve a custom kernel
driver which replaces the generic serial driver. In practice, these
things aren't really a tty, but somethiung else weird, and trying to
do this in userspace seems really awkward.
> setserial (TIOCSSERIAL) and termios (TCSETS*) both require file descriptors,
> so that is not suitable. The 8250 driver, but *not* other serial drivers,
> allows the setserial information to be accessed via sysfs; however, this
> functionality is local to the 8250 driver.
My suggestion of using setserial to turn on some "not really a tty;
but some weird networking / cheap debugging hack" flag should work,
because you would do this at boot up. Note that the 8250
autoconfiguration code (see drivers/tty/serial/8250/8250_port.c) is
going to mess with DTR / RTS. This is why I asserted that trying to
claim that you can preserve "state" across reboots is Just Not
Possible.
If you have some weird setup where DTR or RTS is wierd to the
"detonate the TNT" line, might I suggest that maybe we shouldn't be
using the tty / 8250 serial driver, but it should ***really*** be a
dedicated kernel driver?
- Ted
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: Maciej W. Rozycki @ 2025-11-09 20:43 UTC (permalink / raw)
To: H. Peter Anvin; +Cc: linux-serial, linux-api, LKML
In-Reply-To: <bb44f856-10a2-40c7-a3f7-be50c8e4b0a9@zytor.com>
On Thu, 6 Nov 2025, H. Peter Anvin wrote:
> It seems to me that this may very well be a problem beyond ttys, in which case
> a new open flag to request to a driver that the configuration and (observable)
> state of the underlying hardware device -- whatever it may be -- should not be
> disturbed by calling open(). This is of course already the case for many
> devices, not to mention block and non-devices, in which case this flag is a
> don't care.
FWIW I find using an open flag the most natural way to solve this problem
and I disagree with a view that a 50+ year old standard has to prevent us
from handling new use cases found as the world has changed. We do need to
comply with the standard for the devices that use it, but I think a flag
to opt out is a perfectly sane approach.
Yes, some hardware has limitations and may have to conclude we can't do
anything about it. Just as, say, we can't choose an arbitrary baud rate
with the dz.c driver, because the hardware handled has a 4-bit selector
for a set of predefined rates (to stay remotely on topic). That does not
prevent us from handling more flexible hardware in a way that makes full
use of its features.
> The best name I came up with was O_NRESETDEV, but it's not something I'm
> particularly attached to.
I'd suggest a generic name such as O_RAW for an agnostic way to express a
request not to fiddle with the device in any way regardless of its kind,
i.e. for possible reuse with anything.
> If the opinion is that this *doesn't* have a scope beyond ttys, then perhaps
> abusing the O_DIRECT flag for this purpose would be an alternative.
It seems like a hack to me, but if carefully evaluated we could reuse the
bit encoding. Either way I'd encourage defining a new meaningful name for
the new application of the flag, such as one proposed above.
Maciej
^ permalink raw reply
* Re: [PATCH v5 00/22] Live Update Orchestrator
From: Pasha Tatashin @ 2025-11-09 2:31 UTC (permalink / raw)
To: Andrew Morton
Cc: pratyush, jasonmiu, graf, rppt, dmatlack, rientjes, corbet,
rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <20251108103655.1c89f05222ba06e24ddc3cc3@linux-foundation.org>
> No prob.
>
> It's unfortunate that one has to take unexpected steps (disable
> CONFIG_DEFERRED_STRUCT_PAGE_INIT) just to compile test this.
>
> It's a general thing. I'm increasingly unhappy about how poor
> allmodconfig coverage is, so I'm starting to maintain a custom .config
> to give improved coverage.
That's an interesting point. The depends on !DEFERRED_STRUCT_PAGE_INIT
reduces build and potentially other automatic test coverage for
LUO/KHO.
We should prioritize relaxing this constraint. There are a few
possible short- and long-term solutions, which I will discuss with
Mike and Pratyush at our next sync.
Thanks,
Pasha
^ permalink raw reply
* Re: RFC: Serial port DTR/RTS - O_NRESETDEV
From: H. Peter Anvin @ 2025-11-09 2:25 UTC (permalink / raw)
To: Theodore Ts'o; +Cc: linux-serial, linux-api, LKML
In-Reply-To: <20251107173743.GA3131573@mit.edu>
On 2025-11-07 09:37, Theodore Ts'o wrote:
> On Thu, Nov 06, 2025 at 11:53:23PM -0800, H. Peter Anvin wrote:
>>
>> I recently ran into a pretty serious issue due to the Unix/Linux
>> (mis)behavior of forcing DTR and RTS asserted when a serial port is
>> set, losing the pre-existing status in the process.
>
> There's a hidden assumption in your problem statement which is that
> DTR / RTS has a "state" which can be saved when the serial port is not
> active, where active is one or more file descriptors holding the
> serial port open. There may be certain hardware or drivers where this
> is just not possible, because nothing is defined if the serial port is
> not active. It might make sense if you are using a 8250 UART, but not
> all the world is the National Semiconductor (or clones) UART.
>
> Certainly the "state" will not be preserved across boots, since how we
> autodetect the UART is going to mess with UART settings. So
> *presumably* what you are talking about is you want to be able to open
> the serial port, mess with DTR / RTS, and then be able to close the
> serial port, and then later on, re-open the serial port, have the DTR
> / RTS remain the same. And it's Too Hard(tm) to have userspace
> keeping a file descriptor open during the whole time? (Which is
> traditionally how Unix/Linux has required that applications do
> things.)
>
> Is that a fair summary of the requirements?
>
Not really.
First of all, obviously virtual serial connections that don't fully emulate
RS232/422/485 obviously may have other requirements, however, the ones that
*do* currently have a problem, see for example:
https://lore.kernel.org/linux-serial/20220531043356.8CAB637401A9@freecalypso.org/
RS232 is rarely used these days with its original purpose, modems (except as a
virtual port for things like GSM), but the ubiquitousness of the interface
means it is used for a ton of other things.
The standard ESP32 configuration for its serial port is that asserting RTS#
even for a moment will cause a device reset, and asserting DTR# during reset
forces the device into boot mode. So even if you execute TIOCMSET immediately
after opening the device, you will have glitched the output, and only the
capacitance of the output will save you, in the best case.
The use of RTS# and DTR# as a reset and/or debug mode entry for embedded
devices is, in fact, extremely common; another example is the Atmel single pin
reset/debug interface.
Another example is when the device is connected to an RS485 interface: in that
case asserting RTS# will activate the transmitter, disrupting traffic on the
bus. The kernel will manage RTS# *once it has been configured*, but until the
kernel has been told that the port is used to drive an RS485 port, it has no
way to know.
Furthermore, *even if* the kernel already knew the state and could have
reported it with TIOCMGET, that state is now lost.
It is not correct that the state cannot be maintained across system reboots
(without power loss.) The hardware may or may not allow the state to be read
back (notably, the USB CDC ACM specification, oddly enough, has a
GET_LINE_CODING command but no GET_CONTROL_LINE_STATE) but again, for *those
that can* it should be possible. For those that cannot, there won't be any way
to get valid data to TIOCMGET, but it is still possible to not send a change
command. Either way, the power up state of write-only devices can generally be
assumed to be RTS# and DTR# deasserted, not asserted.
(USB is also a bit special because it is normal for the USB host to power
cycle the device during bus initialization.)
>> It seems to me that this may very well be a problem beyond ttys, in
>> which case a new open flag to request to a driver that the
>> configuration and (observable) state of the underlying hardware
>> device -- whatever it may be -- should not be disturbed by calling
>> open(). This is of course already the case for many devices, not to
>> mention block and non-devices, in which case this flag is a don't
>> care.
>
> I think it's going to be a lot simpler to keep this specific to serial
> ports and DTR / RTS, because the concept that the hardware should not
> be changed when the file descriptor is opened may simply not be
> possible. For example, it might be that until you open it, the there
> might not even be power applied to the device. The concept that all
> hardware should burn battery power once the machine is booted may not
> make sense, and the assumption that hardware has the extra
> millicent(s) worth of silicon to maintain state when power is dropped
> may again, not be something that we can assume as being possible for
> all devices.
This is actually a great example! One should be able to open a file descriptor
to such a device to configure the driver, without needing to power up the
physical hardware.
However, I intentionally defined this as a best-effort control for two reasons:
1. As you say, the hardware may not be able to do it;
2. It will take time until a significant set of drivers can implement this.
> If that's the case, if you want to have something where DTR and RTS
> stay the same, and for some reason we can't assume that userspace
> can't just keep a process holding the tty device open, my suggestion is to use
>
> Given that DTR and RTS are secial port concepts, my suggesiton is to
> set a serial port flag, using setserial(8). It may be the case that
> for certain types of serial device, the attempt to set the flag may be
> rejected, but that's something which the ioctl used by setserial
> already can do and which userspace applications such as setserial
> understand may be the case.
setserial (TIOCSSERIAL) and termios (TCSETS*) both require file descriptors,
so that is not suitable. The 8250 driver, but *not* other serial drivers,
allows the setserial information to be accessed via sysfs; however, this
functionality is local to the 8250 driver.
(Incidentally: the only way to find out the type of a tty driver in the
current Linux kernel is to parse /proc/tty/drivers. This information is
neither available in sysfs nor via ioctl.
Consider the case of a terminal program wanting to display a list of serial
ports. Right now, some serial drivers -- notably the generic UART driver --
will create device nodes for all available minors, exactly so you would be
able to manually attach a device with TIOCSSERIAL. There is no driver-generic
way to find out if there is a hardware device configured other than opening
the device and calling TCGETS or TIOCMGET (depending on exactly what you are
looking for) and see if you get EIO back.
The problem here really isn't the need for a file descriptor -- file
descriptors are The Unix Way[TM] to refer to almost any kind of entity after
all -- but that the act of obtaining the file descriptor -- open -- causes a
direct action as well as loss of existing state.
Now, we obviously can't disable the classical terminal behavior
unconditionally -- that would break a whole lot of perfectly valid code.
Using a sysfs attribute is reasonable on the surface of it (and is what the
patchset linked to above implements) I believe this is the wrong approach,
because it is modal on the device level, and that makes it racy: one program
comes in, flips the attribute, then another program comes in and tries to open
the same device for whatever reason. This opens up at least three possible
race conditions:
- Process 1 sets the nreset bit;
- Process 2 opens the device, not expecting the nreset bit.
- Process 1 reads the nreset bit, trying to be a good citizen;
- Process 1 sets the nreset bit;
- Process 2 reads the nreset bit, ditto;
- ... other stuff happens ...
- Process 1 restores the nreset bit
- Process 2 restores the nreset bit, incorrectly setting it to 1
- Process 1 sets the nreset bit;
- Process 2 sets the nreset bit;
- Process 1 does its work;
- Process 1 clears the nreset bit;
- Process 2 opens the device.
Oh, yes, let's not forget: you need a file descriptor to lock the device for
exclusive use.
This is why I believe this:
1. Needs to be atomic with open(), as opposed to tied to per-device state;
2. Likely can have applications beyond the serial port space, and it would
be a good idea to have a uniform interface. We can discuss the proper
behavior for a device which cannot comply; the best way probably would be
to refuse the open and return an error, which would also happen on older
kernels without this functionality.
Does this make more sense?
-hpa
^ permalink raw reply
* Re: [PATCH v5 00/22] Live Update Orchestrator
From: Andrew Morton @ 2025-11-08 18:36 UTC (permalink / raw)
To: Pasha Tatashin
Cc: pratyush, jasonmiu, graf, rppt, dmatlack, rientjes, corbet,
rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <CA+CK2bCakoNEHk-fgjpnHpo5jtBoXvnzdeJHQOOBBFM8yo-4zQ@mail.gmail.com>
On Sat, 8 Nov 2025 13:13:32 -0500 Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> On Fri, Nov 7, 2025 at 5:33 PM Andrew Morton <akpm@linux-foundation.org> wrote:
> >
> > On Fri, 7 Nov 2025 16:02:58 -0500 Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> >
> > > This series introduces the Live Update Orchestrator, a kernel subsystem
> > > designed to facilitate live kernel updates using a kexec-based reboot.
> >
> > I added this to mm.git's mm-nonmm-stable branch for some linux-next
> > exposure. The usual Cc's were suppressed because there would have been
> > so many of them.
>
> Thank you!
>
No prob.
It's unfortunate that one has to take unexpected steps (disable
CONFIG_DEFERRED_STRUCT_PAGE_INIT) just to compile test this.
It's a general thing. I'm increasingly unhappy about how poor
allmodconfig coverage is, so I'm starting to maintain a custom .config
to give improved coverage.
^ permalink raw reply
* Re: [PATCH v5 00/22] Live Update Orchestrator
From: Pasha Tatashin @ 2025-11-08 18:13 UTC (permalink / raw)
To: Andrew Morton
Cc: pratyush, jasonmiu, graf, rppt, dmatlack, rientjes, corbet,
rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <20251107143310.8b03e72c8f9998ff4c02a0d0@linux-foundation.org>
On Fri, Nov 7, 2025 at 5:33 PM Andrew Morton <akpm@linux-foundation.org> wrote:
>
> On Fri, 7 Nov 2025 16:02:58 -0500 Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
>
> > This series introduces the Live Update Orchestrator, a kernel subsystem
> > designed to facilitate live kernel updates using a kexec-based reboot.
>
> I added this to mm.git's mm-nonmm-stable branch for some linux-next
> exposure. The usual Cc's were suppressed because there would have been
> so many of them.
Thank you!
Pasha
^ permalink raw reply
* Re: [PATCH v5 00/22] Live Update Orchestrator
From: Andrew Morton @ 2025-11-07 22:33 UTC (permalink / raw)
To: Pasha Tatashin
Cc: pratyush, jasonmiu, graf, rppt, dmatlack, rientjes, corbet,
rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl, masahiroy, tj,
yoann.congal, mmaurer, roman.gushchin, chenridong, axboe,
mark.rutland, jannh, vincent.guittot, hannes, dan.j.williams,
david, joel.granados, rostedt, anna.schumaker, song, zhangguopeng,
linux, linux-kernel, linux-doc, linux-mm, gregkh, tglx, mingo, bp,
dave.hansen, x86, hpa, rafael, dakr, bartosz.golaszewski,
cw00.choi, myungjoo.ham, yesanishhere, Jonathan.Cameron,
quic_zijuhu, aleksander.lobakin, ira.weiny, andriy.shevchenko,
leon, lukas, bhelgaas, wagi, djeffery, stuart.w.hayes, ptyadav,
lennart, brauner, linux-api, linux-fsdevel, saeedm, ajayachandra,
jgg, parav, leonro, witu, hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-1-pasha.tatashin@soleen.com>
On Fri, 7 Nov 2025 16:02:58 -0500 Pasha Tatashin <pasha.tatashin@soleen.com> wrote:
> This series introduces the Live Update Orchestrator, a kernel subsystem
> designed to facilitate live kernel updates using a kexec-based reboot.
I added this to mm.git's mm-nonmm-stable branch for some linux-next
exposure. The usual Cc's were suppressed because there would have been
so many of them.
^ permalink raw reply
* [PATCH v5 22/22] tests/liveupdate: Add in-kernel liveupdate test
From: Pasha Tatashin @ 2025-11-07 21:03 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, pasha.tatashin, rppt, dmatlack,
rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl,
masahiroy, akpm, tj, yoann.congal, mmaurer, roman.gushchin,
chenridong, axboe, mark.rutland, jannh, vincent.guittot, hannes,
dan.j.williams, david, joel.granados, rostedt, anna.schumaker,
song, zhangguopeng, linux, linux-kernel, linux-doc, linux-mm,
gregkh, tglx, mingo, bp, dave.hansen, x86, hpa, rafael, dakr,
bartosz.golaszewski, cw00.choi, myungjoo.ham, yesanishhere,
Jonathan.Cameron, quic_zijuhu, aleksander.lobakin, ira.weiny,
andriy.shevchenko, leon, lukas, bhelgaas, wagi, djeffery,
stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu,
hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-1-pasha.tatashin@soleen.com>
Introduce an in-kernel test module to validate the core logic of the
Live Update Orchestrator's File-Lifecycle-Bound feature. This
provides a low-level, controlled environment to test FLB registration
and callback invocation without requiring userspace interaction or
actual kexec reboots.
The test is enabled by the CONFIG_LIVEUPDATE_TEST Kconfig option.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_file.c | 2 +
kernel/liveupdate/luo_internal.h | 8 ++
lib/Kconfig.debug | 23 ++++++
lib/tests/Makefile | 1 +
lib/tests/liveupdate.c | 130 +++++++++++++++++++++++++++++++
5 files changed, 164 insertions(+)
create mode 100644 lib/tests/liveupdate.c
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 713069b96278..4c0a75918f3d 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -829,6 +829,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
INIT_LIST_HEAD(&fh->flb_list);
list_add_tail(&fh->list, &luo_file_handler_list);
+ liveupdate_test_register(fh);
+
return 0;
}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 89c2fd97e5a7..be8986f7ac9b 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -90,4 +90,12 @@ int __init luo_flb_setup_outgoing(void *fdt);
int __init luo_flb_setup_incoming(void *fdt);
void luo_flb_serialize(void);
+#ifdef CONFIG_LIVEUPDATE_TEST
+void liveupdate_test_register(struct liveupdate_file_handler *h);
+#else
+static inline void liveupdate_test_register(struct liveupdate_file_handler *h)
+{
+}
+#endif
+
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ddacf9e665a2..2cbfa3dead0b 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2813,6 +2813,29 @@ config LINEAR_RANGES_TEST
If unsure, say N.
+config LIVEUPDATE_TEST
+ bool "Live Update Kernel Test"
+ default n
+ depends on LIVEUPDATE
+ help
+ Enable a built-in kernel test module for the Live Update
+ Orchestrator.
+
+ This module validates the File-Lifecycle-Bound subsystem by
+ registering a set of mock FLB objects with any real file handlers
+ that support live update (such as the memfd handler).
+
+ When live update operations are performed, this test module will
+ output messages to the kernel log (dmesg), confirming that its
+ registration and various callback functions (preserve, retrieve,
+ finish, etc.) are being invoked correctly.
+
+ This is a debugging and regression testing tool for developers
+ working on the Live Update subsystem. It should not be enabled in
+ production kernels.
+
+ If unsure, say N
+
config CMDLINE_KUNIT_TEST
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index f7460831cfdd..8e5c527a94ac 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o
obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
+obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o
CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
new file mode 100644
index 000000000000..62c592aa859f
--- /dev/null
+++ b/lib/tests/liveupdate.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME " test: " fmt
+
+#include <linux/init.h>
+#include <linux/liveupdate.h>
+#include <linux/module.h>
+#include "../../kernel/liveupdate/luo_internal.h"
+
+#define TEST_NFLBS 3
+#define TEST_FLB_MAGIC_BASE 0xFEEDF00DCAFEBEE0ULL
+
+static struct liveupdate_flb test_flbs[TEST_NFLBS];
+
+static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+
+ pr_info("%s: preserve was triggered\n", argp->flb->compatible);
+ argp->data = TEST_FLB_MAGIC_BASE + index;
+
+ return 0;
+}
+
+static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
+{
+ pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
+}
+
+static void test_flb_retrieve(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+ u64 expected_data = TEST_FLB_MAGIC_BASE + index;
+
+ if (argp->data == expected_data) {
+ pr_info("%s: found flb data from the previous boot\n",
+ argp->flb->compatible);
+ argp->obj = (void *)argp->data;
+ } else {
+ pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
+ argp->flb->compatible, argp->data, expected_data);
+ }
+}
+
+static void test_flb_finish(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+ void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
+
+ if (argp->obj == expected_obj)
+ pr_info("%s: finish was triggered\n", argp->flb->compatible);
+ else
+ pr_err("%s: ERROR - finish called with invalid object\n",
+ argp->flb->compatible);
+}
+
+static const struct liveupdate_flb_ops test_flb_ops = {
+ .preserve = test_flb_preserve,
+ .unpreserve = test_flb_unpreserve,
+ .retrieve = test_flb_retrieve,
+ .finish = test_flb_finish,
+};
+
+#define DEFINE_TEST_FLB(i) \
+ { .ops = &test_flb_ops, .compatible = "test-flb-v" #i }
+
+static struct liveupdate_flb test_flbs[TEST_NFLBS] = {
+ DEFINE_TEST_FLB(0),
+ DEFINE_TEST_FLB(1),
+ DEFINE_TEST_FLB(2),
+};
+
+static int __init liveupdate_test_early_init(void)
+{
+ int i;
+
+ if (!liveupdate_enabled())
+ return 0;
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+ void *obj;
+ int err;
+
+ liveupdate_init_flb(flb);
+
+ err = liveupdate_flb_incoming_locked(flb, &obj);
+ if (!err) {
+ liveupdate_flb_incoming_unlock(flb, obj);
+ } else if (err != -ENODATA && err != -ENOENT) {
+ pr_err("liveupdate_flb_incoming_locked for %s failed: %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+ }
+
+ return 0;
+}
+early_initcall(liveupdate_test_early_init);
+
+void liveupdate_test_register(struct liveupdate_file_handler *h)
+{
+ int err, i;
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+
+ err = liveupdate_register_flb(h, flb);
+ if (err)
+ pr_err("Failed to register %s %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+
+ err = liveupdate_register_flb(h, &test_flbs[0]);
+ if (!err || err != -EEXIST) {
+ pr_err("Failed: %s should be already registered, but got err: %pe\n",
+ test_flbs[0].compatible, ERR_PTR(err));
+ }
+
+ pr_info("Registered %d FLBs with file handler: [%s]\n",
+ TEST_NFLBS, h->compatible);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pasha Tatashin <pasha.tatashin@soleen.com>");
+MODULE_DESCRIPTION("In-kernel test for LUO mechanism");
--
2.51.2.1041.gc1ab5b90ca-goog
^ permalink raw reply related
* [PATCH v5 21/22] selftests/liveupdate: Add kexec test for multiple and empty sessions
From: Pasha Tatashin @ 2025-11-07 21:03 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, pasha.tatashin, rppt, dmatlack,
rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl,
masahiroy, akpm, tj, yoann.congal, mmaurer, roman.gushchin,
chenridong, axboe, mark.rutland, jannh, vincent.guittot, hannes,
dan.j.williams, david, joel.granados, rostedt, anna.schumaker,
song, zhangguopeng, linux, linux-kernel, linux-doc, linux-mm,
gregkh, tglx, mingo, bp, dave.hansen, x86, hpa, rafael, dakr,
bartosz.golaszewski, cw00.choi, myungjoo.ham, yesanishhere,
Jonathan.Cameron, quic_zijuhu, aleksander.lobakin, ira.weiny,
andriy.shevchenko, leon, lukas, bhelgaas, wagi, djeffery,
stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu,
hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-1-pasha.tatashin@soleen.com>
Introduce a new kexec-based selftest, luo_kexec_multi_session, to
validate the end-to-end lifecycle of a more complex LUO scenario.
While the existing luo_kexec_simple test covers the basic end-to-end
lifecycle, it is limited to a single session with one preserved file.
This new test significantly expands coverage by verifying LUO's ability
to handle a mixed workload involving multiple sessions, some of which
are intentionally empty. This ensures that the LUO core correctly
preserves and restores the state of all session types across a reboot.
The test validates the following sequence:
Stage 1 (Pre-kexec):
- Creates two empty test sessions (multi-test-empty-1,
multi-test-empty-2).
- Creates a session with one preserved memfd (multi-test-files-1).
- Creates another session with two preserved memfds
(multi-test-files-2), each containing unique data.
- Creates a state-tracking session to manage the transition to
Stage 2.
- Executes a kexec reboot via the helper script.
Stage 2 (Post-kexec):
- Retrieves the state-tracking session to confirm it is in the
post-reboot stage.
- Retrieves all four test sessions (both the empty and non-empty
ones).
- For the non-empty sessions, restores the preserved memfds and
verifies their contents match the original data patterns.
- Finalizes all test sessions and the state session to ensure a clean
teardown and that all associated kernel resources are correctly
released.
This test provides greater confidence in the robustness of the LUO
framework by validating its behavior in a more realistic, multi-faceted
scenario.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
tools/testing/selftests/liveupdate/.gitignore | 1 +
tools/testing/selftests/liveupdate/Makefile | 1 +
.../selftests/liveupdate/luo_multi_session.c | 190 ++++++++++++++++++
3 files changed, 192 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/luo_multi_session.c
diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore
index daeef116174d..42a15a8d5d9e 100644
--- a/tools/testing/selftests/liveupdate/.gitignore
+++ b/tools/testing/selftests/liveupdate/.gitignore
@@ -1,2 +1,3 @@
/liveupdate
/luo_kexec_simple
+/luo_multi_session
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 1563ac84006a..6ee6efeec62d 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -11,6 +11,7 @@ LUO_SHARED_SRCS := luo_test_utils.c
LUO_SHARED_HDRS += luo_test_utils.h
LUO_MANUAL_TESTS += luo_kexec_simple
+LUO_MANUAL_TESTS += luo_multi_session
TEST_FILES += do_kexec.sh
diff --git a/tools/testing/selftests/liveupdate/luo_multi_session.c b/tools/testing/selftests/liveupdate/luo_multi_session.c
new file mode 100644
index 000000000000..c9955f1b6e97
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_multi_session.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ *
+ * A selftest to validate the end-to-end lifecycle of multiple LUO sessions
+ * across a kexec reboot, including empty sessions and sessions with multiple
+ * files.
+ */
+
+#include "luo_test_utils.h"
+
+#define KEXEC_SCRIPT "./do_kexec.sh"
+
+#define SESSION_EMPTY_1 "multi-test-empty-1"
+#define SESSION_EMPTY_2 "multi-test-empty-2"
+#define SESSION_FILES_1 "multi-test-files-1"
+#define SESSION_FILES_2 "multi-test-files-2"
+
+#define MFD1_TOKEN 0x1001
+#define MFD2_TOKEN 0x2002
+#define MFD3_TOKEN 0x3003
+
+#define MFD1_DATA "Data for session files 1"
+#define MFD2_DATA "First file for session files 2"
+#define MFD3_DATA "Second file for session files 2"
+
+#define STATE_SESSION_NAME "kexec_multi_state"
+#define STATE_MEMFD_TOKEN 998
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+ int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd;
+
+ ksft_print_msg("[STAGE 1] Starting pre-kexec setup for multi-session test...\n");
+
+ ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+ create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+ ksft_print_msg("[STAGE 1] Creating empty sessions '%s' and '%s'...\n",
+ SESSION_EMPTY_1, SESSION_EMPTY_2);
+ s_empty1_fd = luo_create_session(luo_fd, SESSION_EMPTY_1);
+ if (s_empty1_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_EMPTY_1);
+
+ s_empty2_fd = luo_create_session(luo_fd, SESSION_EMPTY_2);
+ if (s_empty2_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_EMPTY_2);
+
+ ksft_print_msg("[STAGE 1] Creating session '%s' with one memfd...\n",
+ SESSION_FILES_1);
+
+ s_files1_fd = luo_create_session(luo_fd, SESSION_FILES_1);
+ if (s_files1_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_FILES_1);
+ if (create_and_preserve_memfd(s_files1_fd, MFD1_TOKEN, MFD1_DATA) < 0) {
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD1_TOKEN);
+ }
+
+ ksft_print_msg("[STAGE 1] Creating session '%s' with two memfds...\n",
+ SESSION_FILES_2);
+
+ s_files2_fd = luo_create_session(luo_fd, SESSION_FILES_2);
+ if (s_files2_fd < 0)
+ fail_exit("luo_create_session for '%s'", SESSION_FILES_2);
+ if (create_and_preserve_memfd(s_files2_fd, MFD2_TOKEN, MFD2_DATA) < 0) {
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD2_TOKEN);
+ }
+ if (create_and_preserve_memfd(s_files2_fd, MFD3_TOKEN, MFD3_DATA) < 0) {
+ fail_exit("create_and_preserve_memfd for token %#x",
+ MFD3_TOKEN);
+ }
+
+ ksft_print_msg("[STAGE 1] Executing kexec...\n");
+
+ if (system(KEXEC_SCRIPT) != 0)
+ fail_exit("kexec script failed");
+
+ exit(EXIT_FAILURE);
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+ int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd;
+ int mfd1, mfd2, mfd3, stage;
+
+ ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+ restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+ if (stage != 2) {
+ fail_exit("Expected stage 2, but state file contains %d",
+ stage);
+ }
+
+ ksft_print_msg("[STAGE 2] Retrieving all sessions...\n");
+ s_empty1_fd = luo_retrieve_session(luo_fd, SESSION_EMPTY_1);
+ if (s_empty1_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", SESSION_EMPTY_1);
+
+ s_empty2_fd = luo_retrieve_session(luo_fd, SESSION_EMPTY_2);
+ if (s_empty2_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", SESSION_EMPTY_2);
+
+ s_files1_fd = luo_retrieve_session(luo_fd, SESSION_FILES_1);
+ if (s_files1_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", SESSION_FILES_1);
+
+ s_files2_fd = luo_retrieve_session(luo_fd, SESSION_FILES_2);
+ if (s_files2_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", SESSION_FILES_2);
+
+ ksft_print_msg("[STAGE 2] Verifying contents of session '%s'...\n",
+ SESSION_FILES_1);
+ mfd1 = restore_and_verify_memfd(s_files1_fd, MFD1_TOKEN, MFD1_DATA);
+ if (mfd1 < 0)
+ fail_exit("restore_and_verify_memfd for token %#x", MFD1_TOKEN);
+ close(mfd1);
+
+ ksft_print_msg("[STAGE 2] Verifying contents of session '%s'...\n",
+ SESSION_FILES_2);
+
+ mfd2 = restore_and_verify_memfd(s_files2_fd, MFD2_TOKEN, MFD2_DATA);
+ if (mfd2 < 0)
+ fail_exit("restore_and_verify_memfd for token %#x", MFD2_TOKEN);
+ close(mfd2);
+
+ mfd3 = restore_and_verify_memfd(s_files2_fd, MFD3_TOKEN, MFD3_DATA);
+ if (mfd3 < 0)
+ fail_exit("restore_and_verify_memfd for token %#x", MFD3_TOKEN);
+ close(mfd3);
+
+ ksft_print_msg("[STAGE 2] Test data verified successfully.\n");
+
+ ksft_print_msg("[STAGE 2] Finalizing all test sessions...\n");
+ if (luo_session_finish(s_empty1_fd) < 0)
+ fail_exit("luo_session_finish for '%s'", SESSION_EMPTY_1);
+ close(s_empty1_fd);
+
+ if (luo_session_finish(s_empty2_fd) < 0)
+ fail_exit("luo_session_finish for '%s'", SESSION_EMPTY_2);
+ close(s_empty2_fd);
+
+ if (luo_session_finish(s_files1_fd) < 0)
+ fail_exit("luo_session_finish for '%s'", SESSION_FILES_1);
+ close(s_files1_fd);
+
+ if (luo_session_finish(s_files2_fd) < 0)
+ fail_exit("luo_session_finish for '%s'", SESSION_FILES_2);
+ close(s_files2_fd);
+
+ ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+ if (luo_session_finish(state_session_fd) < 0)
+ fail_exit("luo_session_finish for state session");
+ close(state_session_fd);
+
+ ksft_print_msg("\n--- MULTI-SESSION KEXEC TEST PASSED ---\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int luo_fd;
+ int state_session_fd;
+
+ luo_fd = luo_open_device();
+ if (luo_fd < 0)
+ ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n",
+ LUO_DEVICE);
+
+ /*
+ * Determine the stage by attempting to retrieve the state session.
+ * If it doesn't exist (ENOENT), we are in Stage 1 (pre-kexec).
+ */
+ state_session_fd = luo_retrieve_session(luo_fd, STATE_SESSION_NAME);
+ if (state_session_fd == -ENOENT) {
+ run_stage_1(luo_fd);
+ } else if (state_session_fd >= 0) {
+ /* We got a valid handle, pass it directly to stage 2 */
+ run_stage_2(luo_fd, state_session_fd);
+ } else {
+ fail_exit("Failed to check for state session");
+ }
+
+ close(luo_fd);
+ return 0;
+}
--
2.51.2.1041.gc1ab5b90ca-goog
^ permalink raw reply related
* [PATCH v5 20/22] selftests/liveupdate: Add kexec-based selftest for session lifecycle
From: Pasha Tatashin @ 2025-11-07 21:03 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, pasha.tatashin, rppt, dmatlack,
rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl,
masahiroy, akpm, tj, yoann.congal, mmaurer, roman.gushchin,
chenridong, axboe, mark.rutland, jannh, vincent.guittot, hannes,
dan.j.williams, david, joel.granados, rostedt, anna.schumaker,
song, zhangguopeng, linux, linux-kernel, linux-doc, linux-mm,
gregkh, tglx, mingo, bp, dave.hansen, x86, hpa, rafael, dakr,
bartosz.golaszewski, cw00.choi, myungjoo.ham, yesanishhere,
Jonathan.Cameron, quic_zijuhu, aleksander.lobakin, ira.weiny,
andriy.shevchenko, leon, lukas, bhelgaas, wagi, djeffery,
stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu,
hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-1-pasha.tatashin@soleen.com>
Introduce a kexec-based selftest, luo_kexec_simple, to validate the
end-to-end lifecycle of a Live Update Orchestrator (LUO) session across
a reboot.
While existing tests verify the uAPI in a pre-reboot context, this test
ensures that the core functionality—preserving state via Kexec Handover
and restoring it in a new kernel—works as expected.
The test operates in two stages, managing its state across the reboot by
preserving a dedicated "state session" containing a memfd. This
mechanism dogfoods the LUO feature itself for state tracking, making the
test self-contained.
The test validates the following sequence:
Stage 1 (Pre-kexec):
- Creates a test session (test-session).
- Creates and preserves a memfd with a known data pattern into the test
session.
- Creates the state-tracking session to signal progression to Stage 2.
- Executes a kexec reboot via a helper script.
Stage 2 (Post-kexec):
- Retrieves the state-tracking session to confirm it is in the
post-reboot stage.
- Retrieves the preserved test session.
- Restores the memfd from the test session and verifies its contents
match the original data pattern written in Stage 1.
- Finalizes both the test and state sessions to ensure a clean
teardown.
The test relies on a helper script (do_kexec.sh) to perform the reboot
and a shared utility library (luo_test_utils.c) for common LUO
operations, keeping the main test logic clean and focused.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
tools/testing/selftests/liveupdate/.gitignore | 1 +
tools/testing/selftests/liveupdate/Makefile | 32 ++++
.../testing/selftests/liveupdate/do_kexec.sh | 6 +
.../selftests/liveupdate/luo_kexec_simple.c | 114 ++++++++++++
.../selftests/liveupdate/luo_test_utils.c | 168 ++++++++++++++++++
.../selftests/liveupdate/luo_test_utils.h | 39 ++++
6 files changed, 360 insertions(+)
create mode 100755 tools/testing/selftests/liveupdate/do_kexec.sh
create mode 100644 tools/testing/selftests/liveupdate/luo_kexec_simple.c
create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.c
create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.h
diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore
index af6e773cf98f..daeef116174d 100644
--- a/tools/testing/selftests/liveupdate/.gitignore
+++ b/tools/testing/selftests/liveupdate/.gitignore
@@ -1 +1,2 @@
/liveupdate
+/luo_kexec_simple
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
index 2a573c36016e..1563ac84006a 100644
--- a/tools/testing/selftests/liveupdate/Makefile
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -1,7 +1,39 @@
# SPDX-License-Identifier: GPL-2.0-only
+
+KHDR_INCLUDES ?= -I../../../../usr/include
CFLAGS += -Wall -O2 -Wno-unused-function
CFLAGS += $(KHDR_INCLUDES)
+LDFLAGS += -static
+OUTPUT ?= .
+
+# --- Test Configuration (Edit this section when adding new tests) ---
+LUO_SHARED_SRCS := luo_test_utils.c
+LUO_SHARED_HDRS += luo_test_utils.h
+
+LUO_MANUAL_TESTS += luo_kexec_simple
+
+TEST_FILES += do_kexec.sh
TEST_GEN_PROGS += liveupdate
+# --- Automatic Rule Generation (Do not edit below) ---
+
+TEST_GEN_PROGS_EXTENDED += $(LUO_MANUAL_TESTS)
+
+# Define the full list of sources for each manual test.
+$(foreach test,$(LUO_MANUAL_TESTS), \
+ $(eval $(test)_SOURCES := $(test).c $(LUO_SHARED_SRCS)))
+
+# This loop automatically generates an explicit build rule for each manual test.
+# It includes dependencies on the shared headers and makes the output
+# executable.
+# Note the use of '$$' to escape automatic variables for the 'eval' command.
+$(foreach test,$(LUO_MANUAL_TESTS), \
+ $(eval $(OUTPUT)/$(test): $($(test)_SOURCES) $(LUO_SHARED_HDRS) \
+ $(call msg,LINK,,$$@) ; \
+ $(Q)$(LINK.c) $$^ $(LDLIBS) -o $$@ ; \
+ $(Q)chmod +x $$@ \
+ ) \
+)
+
include ../lib.mk
diff --git a/tools/testing/selftests/liveupdate/do_kexec.sh b/tools/testing/selftests/liveupdate/do_kexec.sh
new file mode 100755
index 000000000000..bb396a92c3b8
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/do_kexec.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+set -e
+
+kexec -l -s --reuse-cmdline /boot/bzImage
+kexec -e
diff --git a/tools/testing/selftests/liveupdate/luo_kexec_simple.c b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
new file mode 100644
index 000000000000..67ab6ebf9eec
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ *
+ * A simple selftest to validate the end-to-end lifecycle of a LUO session
+ * across a single kexec reboot.
+ */
+
+#include "luo_test_utils.h"
+
+/* Test-specific constants are now defined locally */
+#define KEXEC_SCRIPT "./do_kexec.sh"
+#define TEST_SESSION_NAME "test-session"
+#define TEST_MEMFD_TOKEN 0x1A
+#define TEST_MEMFD_DATA "hello kexec world"
+
+/* Constants for the state-tracking mechanism, specific to this test file. */
+#define STATE_SESSION_NAME "kexec_simple_state"
+#define STATE_MEMFD_TOKEN 999
+
+/* Stage 1: Executed before the kexec reboot. */
+static void run_stage_1(int luo_fd)
+{
+ int session_fd;
+
+ ksft_print_msg("[STAGE 1] Starting pre-kexec setup...\n");
+
+ ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
+ create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
+
+ ksft_print_msg("[STAGE 1] Creating session '%s' and preserving memfd...\n",
+ TEST_SESSION_NAME);
+ session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME);
+ if (session_fd < 0)
+ fail_exit("luo_create_session for '%s'", TEST_SESSION_NAME);
+
+ if (create_and_preserve_memfd(session_fd, TEST_MEMFD_TOKEN,
+ TEST_MEMFD_DATA) < 0) {
+ fail_exit("create_and_preserve_memfd for token %#x",
+ TEST_MEMFD_TOKEN);
+ }
+
+ ksft_print_msg("[STAGE 1] Executing kexec...\n");
+ if (system(KEXEC_SCRIPT) != 0)
+ fail_exit("kexec script failed");
+ exit(EXIT_FAILURE);
+}
+
+/* Stage 2: Executed after the kexec reboot. */
+static void run_stage_2(int luo_fd, int state_session_fd)
+{
+ int session_fd, mfd, stage;
+
+ ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
+
+ restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
+ if (stage != 2)
+ fail_exit("Expected stage 2, but state file contains %d", stage);
+
+ ksft_print_msg("[STAGE 2] Retrieving session '%s'...\n", TEST_SESSION_NAME);
+ session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
+ if (session_fd < 0)
+ fail_exit("luo_retrieve_session for '%s'", TEST_SESSION_NAME);
+
+ ksft_print_msg("[STAGE 2] Restoring and verifying memfd (token %#x)...\n",
+ TEST_MEMFD_TOKEN);
+ mfd = restore_and_verify_memfd(session_fd, TEST_MEMFD_TOKEN,
+ TEST_MEMFD_DATA);
+ if (mfd < 0)
+ fail_exit("restore_and_verify_memfd for token %#x", TEST_MEMFD_TOKEN);
+ close(mfd);
+
+ ksft_print_msg("[STAGE 2] Test data verified successfully.\n");
+ ksft_print_msg("[STAGE 2] Finalizing test session...\n");
+ if (luo_session_finish(session_fd) < 0)
+ fail_exit("luo_session_finish for test session");
+ close(session_fd);
+
+ ksft_print_msg("[STAGE 2] Finalizing state session...\n");
+ if (luo_session_finish(state_session_fd) < 0)
+ fail_exit("luo_session_finish for state session");
+ close(state_session_fd);
+
+ ksft_print_msg("\n--- SIMPLE KEXEC TEST PASSED ---\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int luo_fd;
+ int state_session_fd;
+
+ luo_fd = luo_open_device();
+ if (luo_fd < 0)
+ ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n",
+ LUO_DEVICE);
+
+ /*
+ * Determine the stage by attempting to retrieve the state session.
+ * If it doesn't exist (ENOENT), we are in Stage 1 (pre-kexec).
+ */
+ state_session_fd = luo_retrieve_session(luo_fd, STATE_SESSION_NAME);
+ if (state_session_fd == -ENOENT) {
+ run_stage_1(luo_fd);
+ } else if (state_session_fd >= 0) {
+ /* We got a valid handle, pass it directly to stage 2 */
+ run_stage_2(luo_fd, state_session_fd);
+ } else {
+ fail_exit("Failed to check for state session");
+ }
+
+ close(luo_fd);
+}
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
new file mode 100644
index 000000000000..0a24105cbc54
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include "luo_test_utils.h"
+
+int luo_open_device(void)
+{
+ return open(LUO_DEVICE, O_RDWR);
+}
+
+int luo_create_session(int luo_fd, const char *name)
+{
+ struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
+
+ snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s",
+ LIVEUPDATE_SESSION_NAME_LENGTH - 1, name);
+
+ if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg) < 0)
+ return -errno;
+
+ return arg.fd;
+}
+
+int luo_retrieve_session(int luo_fd, const char *name)
+{
+ struct liveupdate_ioctl_retrieve_session arg = { .size = sizeof(arg) };
+
+ snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s",
+ LIVEUPDATE_SESSION_NAME_LENGTH - 1, name);
+
+ if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg) < 0)
+ return -errno;
+
+ return arg.fd;
+}
+
+int create_and_preserve_memfd(int session_fd, int token, const char *data)
+{
+ struct liveupdate_session_preserve_fd arg = { .size = sizeof(arg) };
+ long page_size = sysconf(_SC_PAGE_SIZE);
+ void *map = MAP_FAILED;
+ int mfd = -1, ret = -1;
+
+ mfd = memfd_create("test_mfd", 0);
+ if (mfd < 0)
+ return -errno;
+
+ if (ftruncate(mfd, page_size) != 0)
+ goto out;
+
+ map = mmap(NULL, page_size, PROT_WRITE, MAP_SHARED, mfd, 0);
+ if (map == MAP_FAILED)
+ goto out;
+
+ snprintf(map, page_size, "%s", data);
+ munmap(map, page_size);
+
+ arg.fd = mfd;
+ arg.token = token;
+ if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg) < 0)
+ goto out;
+
+ ret = 0;
+out:
+ if (ret != 0 && errno != 0)
+ ret = -errno;
+ if (mfd >= 0)
+ close(mfd);
+ return ret;
+}
+
+int restore_and_verify_memfd(int session_fd, int token,
+ const char *expected_data)
+{
+ struct liveupdate_session_retrieve_fd arg = { .size = sizeof(arg) };
+ long page_size = sysconf(_SC_PAGE_SIZE);
+ void *map = MAP_FAILED;
+ int mfd = -1, ret = -1;
+
+ arg.token = token;
+ if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg) < 0)
+ return -errno;
+ mfd = arg.fd;
+
+ map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0);
+ if (map == MAP_FAILED)
+ goto out;
+
+ if (expected_data && strcmp(expected_data, map) != 0) {
+ ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n",
+ expected_data, (char *)map);
+ ret = -EINVAL;
+ goto out_munmap;
+ }
+
+ ret = mfd;
+out_munmap:
+ munmap(map, page_size);
+out:
+ if (ret < 0 && errno != 0)
+ ret = -errno;
+ if (ret < 0 && mfd >= 0)
+ close(mfd);
+ return ret;
+}
+
+int luo_session_finish(int session_fd)
+{
+ struct liveupdate_session_finish arg = { .size = sizeof(arg) };
+
+ if (ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &arg) < 0)
+ return -errno;
+
+ return 0;
+}
+
+void create_state_file(int luo_fd, const char *session_name, int token,
+ int next_stage)
+{
+ char buf[32];
+ int state_session_fd;
+
+ state_session_fd = luo_create_session(luo_fd, session_name);
+ if (state_session_fd < 0)
+ fail_exit("luo_create_session for state tracking");
+
+ snprintf(buf, sizeof(buf), "%d", next_stage);
+ if (create_and_preserve_memfd(state_session_fd, token, buf) < 0)
+ fail_exit("create_and_preserve_memfd for state tracking");
+
+ /*
+ * DO NOT close session FD, otherwise it is going to be unpreserved
+ */
+}
+
+void restore_and_read_stage(int state_session_fd, int token, int *stage)
+{
+ char buf[32] = {0};
+ int mfd;
+
+ mfd = restore_and_verify_memfd(state_session_fd, token, NULL);
+ if (mfd < 0)
+ fail_exit("failed to restore state memfd");
+
+ if (read(mfd, buf, sizeof(buf) - 1) < 0)
+ fail_exit("failed to read state mfd");
+
+ *stage = atoi(buf);
+
+ close(mfd);
+}
diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
new file mode 100644
index 000000000000..093e787b9f4b
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ *
+ * Utility functions for LUO kselftests.
+ */
+
+#ifndef LUO_TEST_UTILS_H
+#define LUO_TEST_UTILS_H
+
+#include <errno.h>
+#include <string.h>
+#include <linux/liveupdate.h>
+#include "../kselftest.h"
+
+#define LUO_DEVICE "/dev/liveupdate"
+
+#define fail_exit(fmt, ...) \
+ ksft_exit_fail_msg("[%s:%d] " fmt " (errno: %s)\n", \
+ __func__, __LINE__, ##__VA_ARGS__, strerror(errno))
+
+/* Generic LUO and session management helpers */
+int luo_open_device(void);
+int luo_create_session(int luo_fd, const char *name);
+int luo_retrieve_session(int luo_fd, const char *name);
+int luo_session_finish(int session_fd);
+
+/* Generic file preservation and restoration helpers */
+int create_and_preserve_memfd(int session_fd, int token, const char *data);
+int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
+
+/* Kexec state-tracking helpers */
+void create_state_file(int luo_fd, const char *session_name, int token,
+ int next_stage);
+void restore_and_read_stage(int state_session_fd, int token, int *stage);
+
+#endif /* LUO_TEST_UTILS_H */
--
2.51.2.1041.gc1ab5b90ca-goog
^ permalink raw reply related
* [PATCH v5 19/22] selftests/liveupdate: Add userspace API selftests
From: Pasha Tatashin @ 2025-11-07 21:03 UTC (permalink / raw)
To: pratyush, jasonmiu, graf, pasha.tatashin, rppt, dmatlack,
rientjes, corbet, rdunlap, ilpo.jarvinen, kanie, ojeda, aliceryhl,
masahiroy, akpm, tj, yoann.congal, mmaurer, roman.gushchin,
chenridong, axboe, mark.rutland, jannh, vincent.guittot, hannes,
dan.j.williams, david, joel.granados, rostedt, anna.schumaker,
song, zhangguopeng, linux, linux-kernel, linux-doc, linux-mm,
gregkh, tglx, mingo, bp, dave.hansen, x86, hpa, rafael, dakr,
bartosz.golaszewski, cw00.choi, myungjoo.ham, yesanishhere,
Jonathan.Cameron, quic_zijuhu, aleksander.lobakin, ira.weiny,
andriy.shevchenko, leon, lukas, bhelgaas, wagi, djeffery,
stuart.w.hayes, ptyadav, lennart, brauner, linux-api,
linux-fsdevel, saeedm, ajayachandra, jgg, parav, leonro, witu,
hughd, skhawaja, chrisl
In-Reply-To: <20251107210526.257742-1-pasha.tatashin@soleen.com>
Introduce a selftest suite for LUO. These tests validate the core
userspace-facing API provided by the /dev/liveupdate device and its
associated ioctls.
The suite covers fundamental device behavior, session management, and
the file preservation mechanism using memfd as a test case. This
provides regression testing for the LUO uAPI.
The following functionality is verified:
Device Access:
Basic open and close operations on /dev/liveupdate.
Enforcement of exclusive device access (verifying EBUSY on a
second open).
Session Management:
Successful creation of sessions with unique names.
Failure to create sessions with duplicate names.
File Preservation:
Preserving a single memfd and verifying its content remains
intact post-preservation.
Preserving multiple memfds within a single session, each with
unique data.
A complex scenario involving multiple sessions, each containing
a mix of empty and data-filled memfds.
Note: This test suite is limited to verifying the pre-kexec
functionality of LUO (e.g., session creation, file preservation).
The post-kexec restoration of resources is not covered, as the kselftest
framework does not currently support orchestrating a reboot and
continuing execution in the new kernel.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/liveupdate/.gitignore | 1 +
tools/testing/selftests/liveupdate/Makefile | 7 +
tools/testing/selftests/liveupdate/config | 5 +
.../testing/selftests/liveupdate/liveupdate.c | 317 ++++++++++++++++++
6 files changed, 332 insertions(+)
create mode 100644 tools/testing/selftests/liveupdate/.gitignore
create mode 100644 tools/testing/selftests/liveupdate/Makefile
create mode 100644 tools/testing/selftests/liveupdate/config
create mode 100644 tools/testing/selftests/liveupdate/liveupdate.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3ece47c552a8..21cd3c6181c4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14525,6 +14525,7 @@ F: include/linux/liveupdate/
F: include/uapi/linux/liveupdate.h
F: kernel/liveupdate/
F: mm/memfd_luo.c
+F: tools/testing/selftests/liveupdate/
LLC (802.2)
L: netdev@vger.kernel.org
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index c46ebdb9b8ef..56e44a98d6a5 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -54,6 +54,7 @@ TARGETS += kvm
TARGETS += landlock
TARGETS += lib
TARGETS += livepatch
+TARGETS += liveupdate
TARGETS += lkdtm
TARGETS += lsm
TARGETS += membarrier
diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore
new file mode 100644
index 000000000000..af6e773cf98f
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/.gitignore
@@ -0,0 +1 @@
+/liveupdate
diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
new file mode 100644
index 000000000000..2a573c36016e
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+CFLAGS += -Wall -O2 -Wno-unused-function
+CFLAGS += $(KHDR_INCLUDES)
+
+TEST_GEN_PROGS += liveupdate
+
+include ../lib.mk
diff --git a/tools/testing/selftests/liveupdate/config b/tools/testing/selftests/liveupdate/config
new file mode 100644
index 000000000000..c0c7e7cc484e
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/config
@@ -0,0 +1,5 @@
+CONFIG_KEXEC_FILE=y
+CONFIG_KEXEC_HANDOVER=y
+CONFIG_KEXEC_HANDOVER_DEBUGFS=y
+CONFIG_KEXEC_HANDOVER_DEBUG=y
+CONFIG_LIVEUPDATE=y
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
new file mode 100644
index 000000000000..eec26288a102
--- /dev/null
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+/*
+ * Selftests for the Live Update Orchestrator.
+ * This test suite verifies the functionality and behavior of the
+ * /dev/liveupdate character device and its session management capabilities.
+ *
+ * Tests include:
+ * - Device access: basic open/close, and enforcement of exclusive access.
+ * - Session management: creation of unique sessions, and duplicate name detection.
+ * - Resource preservation: successfully preserving individual and multiple memfds,
+ * verifying contents remain accessible.
+ * - Complex multi-session scenarios involving mixed empty and populated files.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <linux/liveupdate.h>
+
+#include "../kselftest.h"
+#include "../kselftest_harness.h"
+
+#define LIVEUPDATE_DEV "/dev/liveupdate"
+
+FIXTURE(liveupdate_device) {
+ int fd1;
+ int fd2;
+};
+
+FIXTURE_SETUP(liveupdate_device)
+{
+ self->fd1 = -1;
+ self->fd2 = -1;
+}
+
+FIXTURE_TEARDOWN(liveupdate_device)
+{
+ if (self->fd1 >= 0)
+ close(self->fd1);
+ if (self->fd2 >= 0)
+ close(self->fd2);
+}
+
+/*
+ * Test Case: Basic Open and Close
+ *
+ * Verifies that the /dev/liveupdate device can be opened and subsequently
+ * closed without errors. Skips if the device does not exist.
+ */
+TEST_F(liveupdate_device, basic_open_close)
+{
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist.", LIVEUPDATE_DEV);
+
+ ASSERT_GE(self->fd1, 0);
+ ASSERT_EQ(close(self->fd1), 0);
+ self->fd1 = -1;
+}
+
+/*
+ * Test Case: Exclusive Open Enforcement
+ *
+ * Verifies that the /dev/liveupdate device can only be opened by one process
+ * at a time. It checks that a second attempt to open the device fails with
+ * the EBUSY error code.
+ */
+TEST_F(liveupdate_device, exclusive_open)
+{
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist.", LIVEUPDATE_DEV);
+
+ ASSERT_GE(self->fd1, 0);
+ self->fd2 = open(LIVEUPDATE_DEV, O_RDWR);
+ EXPECT_LT(self->fd2, 0);
+ EXPECT_EQ(errno, EBUSY);
+}
+
+/* Helper function to create a LUO session via ioctl. */
+static int create_session(int lu_fd, const char *name)
+{
+ struct liveupdate_ioctl_create_session args = {};
+
+ args.size = sizeof(args);
+ strncpy((char *)args.name, name, sizeof(args.name) - 1);
+
+ if (ioctl(lu_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args))
+ return -errno;
+
+ return args.fd;
+}
+
+/*
+ * Test Case: Create Duplicate Session
+ *
+ * Verifies that attempting to create two sessions with the same name fails
+ * on the second attempt with EEXIST.
+ */
+TEST_F(liveupdate_device, create_duplicate_session)
+{
+ int session_fd1, session_fd2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd1 = create_session(self->fd1, "duplicate-session-test");
+ ASSERT_GE(session_fd1, 0);
+
+ session_fd2 = create_session(self->fd1, "duplicate-session-test");
+ EXPECT_LT(session_fd2, 0);
+ EXPECT_EQ(-session_fd2, EEXIST);
+
+ ASSERT_EQ(close(session_fd1), 0);
+}
+
+/*
+ * Test Case: Create Distinct Sessions
+ *
+ * Verifies that creating two sessions with different names succeeds.
+ */
+TEST_F(liveupdate_device, create_distinct_sessions)
+{
+ int session_fd1, session_fd2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd1 = create_session(self->fd1, "distinct-session-1");
+ ASSERT_GE(session_fd1, 0);
+
+ session_fd2 = create_session(self->fd1, "distinct-session-2");
+ ASSERT_GE(session_fd2, 0);
+
+ ASSERT_EQ(close(session_fd1), 0);
+ ASSERT_EQ(close(session_fd2), 0);
+}
+
+static int preserve_fd(int session_fd, int fd_to_preserve, __u64 token)
+{
+ struct liveupdate_session_preserve_fd args = {};
+
+ args.size = sizeof(args);
+ args.fd = fd_to_preserve;
+ args.token = token;
+
+ if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args))
+ return -errno;
+
+ return 0;
+}
+
+/*
+ * Test Case: Preserve MemFD
+ *
+ * Verifies that a valid memfd can be successfully preserved in a session and
+ * that its contents remain intact after the preservation call.
+ */
+TEST_F(liveupdate_device, preserve_memfd)
+{
+ const char *test_str = "hello liveupdate";
+ char read_buf[64] = {};
+ int session_fd, mem_fd;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = create_session(self->fd1, "preserve-memfd-test");
+ ASSERT_GE(session_fd, 0);
+
+ mem_fd = memfd_create("test-memfd", 0);
+ ASSERT_GE(mem_fd, 0);
+
+ ASSERT_EQ(write(mem_fd, test_str, strlen(test_str)), strlen(test_str));
+ ASSERT_EQ(preserve_fd(session_fd, mem_fd, 0x1234), 0);
+ ASSERT_EQ(close(session_fd), 0);
+
+ ASSERT_EQ(lseek(mem_fd, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd, read_buf, sizeof(read_buf)), strlen(test_str));
+ ASSERT_STREQ(read_buf, test_str);
+ ASSERT_EQ(close(mem_fd), 0);
+}
+
+/*
+ * Test Case: Preserve Multiple MemFDs
+ *
+ * Verifies that multiple memfds can be preserved in a single session,
+ * each with a unique token, and that their contents remain distinct and
+ * correct after preservation.
+ */
+TEST_F(liveupdate_device, preserve_multiple_memfds)
+{
+ const char *test_str1 = "data for memfd one";
+ const char *test_str2 = "data for memfd two";
+ char read_buf[64] = {};
+ int session_fd, mem_fd1, mem_fd2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = create_session(self->fd1, "preserve-multi-memfd-test");
+ ASSERT_GE(session_fd, 0);
+
+ mem_fd1 = memfd_create("test-memfd-1", 0);
+ ASSERT_GE(mem_fd1, 0);
+ mem_fd2 = memfd_create("test-memfd-2", 0);
+ ASSERT_GE(mem_fd2, 0);
+
+ ASSERT_EQ(write(mem_fd1, test_str1, strlen(test_str1)), strlen(test_str1));
+ ASSERT_EQ(write(mem_fd2, test_str2, strlen(test_str2)), strlen(test_str2));
+
+ ASSERT_EQ(preserve_fd(session_fd, mem_fd1, 0xAAAA), 0);
+ ASSERT_EQ(preserve_fd(session_fd, mem_fd2, 0xBBBB), 0);
+
+ memset(read_buf, 0, sizeof(read_buf));
+ ASSERT_EQ(lseek(mem_fd1, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd1, read_buf, sizeof(read_buf)), strlen(test_str1));
+ ASSERT_STREQ(read_buf, test_str1);
+
+ memset(read_buf, 0, sizeof(read_buf));
+ ASSERT_EQ(lseek(mem_fd2, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd2, read_buf, sizeof(read_buf)), strlen(test_str2));
+ ASSERT_STREQ(read_buf, test_str2);
+
+ ASSERT_EQ(close(mem_fd1), 0);
+ ASSERT_EQ(close(mem_fd2), 0);
+ ASSERT_EQ(close(session_fd), 0);
+}
+
+/*
+ * Test Case: Preserve Complex Scenario
+ *
+ * Verifies a more complex scenario with multiple sessions and a mix of empty
+ * and non-empty memfds distributed across them.
+ */
+TEST_F(liveupdate_device, preserve_complex_scenario)
+{
+ const char *data1 = "data for session 1";
+ const char *data2 = "data for session 2";
+ char read_buf[64] = {};
+ int session_fd1, session_fd2;
+ int mem_fd_data1, mem_fd_empty1, mem_fd_data2, mem_fd_empty2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd1 = create_session(self->fd1, "complex-session-1");
+ ASSERT_GE(session_fd1, 0);
+ session_fd2 = create_session(self->fd1, "complex-session-2");
+ ASSERT_GE(session_fd2, 0);
+
+ mem_fd_data1 = memfd_create("data1", 0);
+ ASSERT_GE(mem_fd_data1, 0);
+ ASSERT_EQ(write(mem_fd_data1, data1, strlen(data1)), strlen(data1));
+
+ mem_fd_empty1 = memfd_create("empty1", 0);
+ ASSERT_GE(mem_fd_empty1, 0);
+
+ mem_fd_data2 = memfd_create("data2", 0);
+ ASSERT_GE(mem_fd_data2, 0);
+ ASSERT_EQ(write(mem_fd_data2, data2, strlen(data2)), strlen(data2));
+
+ mem_fd_empty2 = memfd_create("empty2", 0);
+ ASSERT_GE(mem_fd_empty2, 0);
+
+ ASSERT_EQ(preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0);
+ ASSERT_EQ(preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0);
+ ASSERT_EQ(preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0);
+ ASSERT_EQ(preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0);
+
+ ASSERT_EQ(lseek(mem_fd_data1, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd_data1, read_buf, sizeof(read_buf)), strlen(data1));
+ ASSERT_STREQ(read_buf, data1);
+
+ memset(read_buf, 0, sizeof(read_buf));
+ ASSERT_EQ(lseek(mem_fd_data2, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd_data2, read_buf, sizeof(read_buf)), strlen(data2));
+ ASSERT_STREQ(read_buf, data2);
+
+ ASSERT_EQ(lseek(mem_fd_empty1, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd_empty1, read_buf, sizeof(read_buf)), 0);
+
+ ASSERT_EQ(lseek(mem_fd_empty2, 0, SEEK_SET), 0);
+ ASSERT_EQ(read(mem_fd_empty2, read_buf, sizeof(read_buf)), 0);
+
+ ASSERT_EQ(close(mem_fd_data1), 0);
+ ASSERT_EQ(close(mem_fd_empty1), 0);
+ ASSERT_EQ(close(mem_fd_data2), 0);
+ ASSERT_EQ(close(mem_fd_empty2), 0);
+ ASSERT_EQ(close(session_fd1), 0);
+ ASSERT_EQ(close(session_fd2), 0);
+}
+
+TEST_HARNESS_MAIN
--
2.51.2.1041.gc1ab5b90ca-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox