[PATCH v7 4/5] um: support suspend to RAM
Anton Ivanov
anton.ivanov at kot-begemot.co.uk
Thu Dec 3 06:34:24 EST 2020
On 02/12/2020 19:58, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg at intel.com>
>
> With all the previous bits in place, we can now also support
> suspend to RAM, in the sense that everything is suspended,
> not just most, including userspace, processes like in s2idle.
>
> Since um_idle_sleep() now waits forever, we can simply call
> that to "suspend" the system.
>
> As before, you can wake it up using SIGUSR1 since we're just
> in a pause() call that only needs to return.
>
> In order to implement selective resume from certain devices,
> and not have any arbitrary device interrupt wake up, suspend
> interrupts by removing SIGIO notification (O_ASYNC) from all
> the FDs that are not supposed to wake up the system. However,
> swap out the handler so we don't actually handle the SIGIO as
> an interrupt.
>
> Since we're in pause(), the mere act of receiving SIGIO wakes
> us up, and then after things have been restored enough, re-set
> O_ASYNC for all previously suspended FDs, reinstall the proper
> SIGIO handler, and send SIGIO to self to process anything that
> might now be pending.
>
> Signed-off-by: Johannes Berg <johannes.berg at intel.com>
> ---
> v7:
> - clean up a bit
> - send SIGIO to self properly, not to the entire group
> ---
> arch/um/include/shared/kern_util.h | 1 +
> arch/um/include/shared/os.h | 3 +
> arch/um/kernel/irq.c | 88 +++++++++++++++++++++++++++++-
> arch/um/kernel/process.c | 2 +-
> arch/um/kernel/um_arch.c | 42 ++++++++++++++
> arch/um/os-Linux/signal.c | 5 ++
> 6 files changed, 139 insertions(+), 2 deletions(-)
>
> diff --git a/arch/um/include/shared/kern_util.h b/arch/um/include/shared/kern_util.h
> index 9c08e728a675..2888ec812f6e 100644
> --- a/arch/um/include/shared/kern_util.h
> +++ b/arch/um/include/shared/kern_util.h
> @@ -68,5 +68,6 @@ extern void bus_handler(int sig, struct siginfo *si, struct uml_pt_regs *regs);
> extern void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs);
> extern void fatal_sigsegv(void) __attribute__ ((noreturn));
>
> +void um_idle_sleep(void);
>
> #endif
> diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
> index 78250a05394a..cd750d4edfb5 100644
> --- a/arch/um/include/shared/os.h
> +++ b/arch/um/include/shared/os.h
> @@ -233,6 +233,7 @@ extern void timer_set_signal_handler(void);
> extern void set_sigstack(void *sig_stack, int size);
> extern void remove_sigstack(void);
> extern void set_handler(int sig);
> +extern void send_sigio_to_self(void);
> extern int change_sig(int signal, int on);
> extern void block_signals(void);
> extern void unblock_signals(void);
> @@ -307,6 +308,8 @@ extern int os_mod_epoll_fd(int events, int fd, void *data);
> extern int os_del_epoll_fd(int fd);
> extern void os_set_ioignore(void);
> extern void os_close_epoll_fd(void);
> +extern void um_irqs_suspend(void);
> +extern void um_irqs_resume(void);
>
> /* sigio.c */
> extern int add_sigio_fd(int fd);
> diff --git a/arch/um/kernel/irq.c b/arch/um/kernel/irq.c
> index 482269580b79..ea43312cbfd3 100644
> --- a/arch/um/kernel/irq.c
> +++ b/arch/um/kernel/irq.c
> @@ -20,6 +20,7 @@
> #include <os.h>
> #include <irq_user.h>
> #include <irq_kern.h>
> +#include <as-layout.h>
>
>
> extern void free_irqs(void);
> @@ -36,12 +37,14 @@ struct irq_reg {
> int events;
> bool active;
> bool pending;
> + bool wakeup;
> };
>
> struct irq_entry {
> struct list_head list;
> int fd;
> struct irq_reg reg[NUM_IRQ_TYPES];
> + bool suspended;
> };
>
> static DEFINE_SPINLOCK(irq_lock);
> @@ -70,6 +73,11 @@ static void irq_io_loop(struct irq_reg *irq, struct uml_pt_regs *regs)
> }
> }
>
> +void sigio_handler_suspend(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
> +{
> + /* nothing */
> +}
> +
> void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
> {
> struct irq_entry *irq_entry;
> @@ -365,9 +373,86 @@ int um_request_irq(int irq, int fd, enum um_irq_type type,
> clear_bit(irq, irqs_allocated);
> return err;
> }
> -
> EXPORT_SYMBOL(um_request_irq);
>
> +#ifdef CONFIG_PM_SLEEP
> +void um_irqs_suspend(void)
> +{
> + struct irq_entry *entry;
> + unsigned long flags;
> +
> + sig_info[SIGIO] = sigio_handler_suspend;
> +
> + spin_lock_irqsave(&irq_lock, flags);
> + list_for_each_entry(entry, &active_fds, list) {
> + enum um_irq_type t;
> + bool wake = false;
> +
> + for (t = 0; t < NUM_IRQ_TYPES; t++) {
> + if (!entry->reg[t].events)
> + continue;
> +
> + if (entry->reg[t].wakeup) {
> + wake = true;
> + break;
> + }
> + }
> +
> + if (!wake) {
> + entry->suspended = true;
> + os_clear_fd_async(entry->fd);
> + }
> + }
> + spin_unlock_irqrestore(&irq_lock, flags);
> +}
> +
> +void um_irqs_resume(void)
> +{
> + struct irq_entry *entry;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&irq_lock, flags);
> + list_for_each_entry(entry, &active_fds, list) {
> + if (entry->suspended) {
> + int err = os_set_fd_async(entry->fd);
> +
> + WARN(err < 0, "os_set_fd_async returned %d\n", err);
> + entry->suspended = false;
> + }
> + }
> + spin_unlock_irqrestore(&irq_lock, flags);
> +
> + sig_info[SIGIO] = sigio_handler;
> + send_sigio_to_self();
> +}
> +
> +static int normal_irq_set_wake(struct irq_data *d, unsigned int on)
> +{
> + struct irq_entry *entry;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&irq_lock, flags);
> + list_for_each_entry(entry, &active_fds, list) {
> + enum um_irq_type t;
> +
> + for (t = 0; t < NUM_IRQ_TYPES; t++) {
> + if (!entry->reg[t].events)
> + continue;
> +
> + if (entry->reg[t].irq != d->irq)
> + continue;
> + entry->reg[t].wakeup = on;
> + goto unlock;
> + }
> + }
> +unlock:
> + spin_unlock_irqrestore(&irq_lock, flags);
> + return 0;
> +}
> +#else
> +#define normal_irq_set_wake NULL
> +#endif
> +
> /*
> * irq_chip must define at least enable/disable and ack when
> * the edge handler is used.
> @@ -384,6 +469,7 @@ static struct irq_chip normal_irq_type = {
> .irq_ack = dummy,
> .irq_mask = dummy,
> .irq_unmask = dummy,
> + .irq_set_wake = normal_irq_set_wake,
> };
>
> static struct irq_chip alarm_irq_type = {
> diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c
> index 0686fabba576..f0f50eae2293 100644
> --- a/arch/um/kernel/process.c
> +++ b/arch/um/kernel/process.c
> @@ -202,7 +202,7 @@ void initial_thread_cb(void (*proc)(void *), void *arg)
> kmalloc_ok = save_kmalloc_ok;
> }
>
> -static void um_idle_sleep(void)
> +void um_idle_sleep(void)
> {
> if (time_travel_mode != TT_MODE_OFF)
> time_travel_sleep();
> diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c
> index 237a8d73a096..9c7e6d7ea1b3 100644
> --- a/arch/um/kernel/um_arch.c
> +++ b/arch/um/kernel/um_arch.c
> @@ -385,6 +385,45 @@ void uml_pm_wake(void)
> pm_system_wakeup();
> }
>
> +static int um_suspend_valid(suspend_state_t state)
> +{
> + return state == PM_SUSPEND_MEM;
> +}
> +
> +static int um_suspend_prepare(void)
> +{
> + um_irqs_suspend();
> + return 0;
> +}
> +
> +static int um_suspend_enter(suspend_state_t state)
> +{
> + if (WARN_ON(state != PM_SUSPEND_MEM))
> + return -EINVAL;
> +
> + /*
> + * This is identical to the idle sleep, but we've just
> + * (during suspend) turned off all interrupt sources
> + * except for the ones we want, so now we can only wake
> + * up on something we actually want to wake up on. All
> + * timing has also been suspended.
> + */
> + um_idle_sleep();
> + return 0;
> +}
> +
> +static void um_suspend_finish(void)
> +{
> + um_irqs_resume();
> +}
> +
> +const struct platform_suspend_ops um_suspend_ops = {
> + .valid = um_suspend_valid,
> + .prepare = um_suspend_prepare,
> + .enter = um_suspend_enter,
> + .finish = um_suspend_finish,
> +};
> +
> static int init_pm_wake_signal(void)
> {
> /*
> @@ -397,6 +436,9 @@ static int init_pm_wake_signal(void)
> */
> if (time_travel_mode != TT_MODE_EXTERNAL)
> register_pm_wake_signal();
> +
> + suspend_set_ops(&um_suspend_ops);
> +
> return 0;
> }
>
> diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c
> index 0a2ea84033b4..510e956b4320 100644
> --- a/arch/um/os-Linux/signal.c
> +++ b/arch/um/os-Linux/signal.c
> @@ -234,6 +234,11 @@ void set_handler(int sig)
> panic("sigprocmask failed - errno = %d\n", errno);
> }
>
> +void send_sigio_to_self(void)
> +{
> + kill(os_getpid(), SIGIO);
> +}
> +
> int change_sig(int signal, int on)
> {
> sigset_t sigset;
>
Anton Ivanov <anton.ivanov at cambridgegreys.com>
--
Anton R. Ivanov
https://www.kot-begemot.co.uk/
More information about the linux-um
mailing list