Commit a374b7cb authored by Johannes Berg's avatar Johannes Berg Committed by Richard Weinberger

um: Support suspend to RAM

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: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarRichard Weinberger <richard@nod.at>
parent 92dcd3d3
...@@ -68,5 +68,6 @@ extern void bus_handler(int sig, struct siginfo *si, struct uml_pt_regs *regs); ...@@ -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 winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs);
extern void fatal_sigsegv(void) __attribute__ ((noreturn)); extern void fatal_sigsegv(void) __attribute__ ((noreturn));
void um_idle_sleep(void);
#endif #endif
...@@ -233,6 +233,7 @@ extern void timer_set_signal_handler(void); ...@@ -233,6 +233,7 @@ extern void timer_set_signal_handler(void);
extern void set_sigstack(void *sig_stack, int size); extern void set_sigstack(void *sig_stack, int size);
extern void remove_sigstack(void); extern void remove_sigstack(void);
extern void set_handler(int sig); extern void set_handler(int sig);
extern void send_sigio_to_self(void);
extern int change_sig(int signal, int on); extern int change_sig(int signal, int on);
extern void block_signals(void); extern void block_signals(void);
extern void unblock_signals(void); extern void unblock_signals(void);
...@@ -307,6 +308,8 @@ extern int os_mod_epoll_fd(int events, int fd, void *data); ...@@ -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 int os_del_epoll_fd(int fd);
extern void os_set_ioignore(void); extern void os_set_ioignore(void);
extern void os_close_epoll_fd(void); extern void os_close_epoll_fd(void);
extern void um_irqs_suspend(void);
extern void um_irqs_resume(void);
/* sigio.c */ /* sigio.c */
extern int add_sigio_fd(int fd); extern int add_sigio_fd(int fd);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <os.h> #include <os.h>
#include <irq_user.h> #include <irq_user.h>
#include <irq_kern.h> #include <irq_kern.h>
#include <as-layout.h>
extern void free_irqs(void); extern void free_irqs(void);
...@@ -36,12 +37,14 @@ struct irq_reg { ...@@ -36,12 +37,14 @@ struct irq_reg {
int events; int events;
bool active; bool active;
bool pending; bool pending;
bool wakeup;
}; };
struct irq_entry { struct irq_entry {
struct list_head list; struct list_head list;
int fd; int fd;
struct irq_reg reg[NUM_IRQ_TYPES]; struct irq_reg reg[NUM_IRQ_TYPES];
bool suspended;
}; };
static DEFINE_SPINLOCK(irq_lock); static DEFINE_SPINLOCK(irq_lock);
...@@ -70,6 +73,11 @@ static void irq_io_loop(struct irq_reg *irq, struct uml_pt_regs *regs) ...@@ -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) void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
{ {
struct irq_entry *irq_entry; struct irq_entry *irq_entry;
...@@ -365,9 +373,86 @@ int um_request_irq(int irq, int fd, enum um_irq_type type, ...@@ -365,9 +373,86 @@ int um_request_irq(int irq, int fd, enum um_irq_type type,
clear_bit(irq, irqs_allocated); clear_bit(irq, irqs_allocated);
return err; return err;
} }
EXPORT_SYMBOL(um_request_irq); 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 * irq_chip must define at least enable/disable and ack when
* the edge handler is used. * the edge handler is used.
...@@ -384,6 +469,7 @@ static struct irq_chip normal_irq_type = { ...@@ -384,6 +469,7 @@ static struct irq_chip normal_irq_type = {
.irq_ack = dummy, .irq_ack = dummy,
.irq_mask = dummy, .irq_mask = dummy,
.irq_unmask = dummy, .irq_unmask = dummy,
.irq_set_wake = normal_irq_set_wake,
}; };
static struct irq_chip alarm_irq_type = { static struct irq_chip alarm_irq_type = {
......
...@@ -203,7 +203,7 @@ void initial_thread_cb(void (*proc)(void *), void *arg) ...@@ -203,7 +203,7 @@ void initial_thread_cb(void (*proc)(void *), void *arg)
kmalloc_ok = save_kmalloc_ok; kmalloc_ok = save_kmalloc_ok;
} }
static void um_idle_sleep(void) void um_idle_sleep(void)
{ {
if (time_travel_mode != TT_MODE_OFF) if (time_travel_mode != TT_MODE_OFF)
time_travel_sleep(); time_travel_sleep();
......
...@@ -385,6 +385,45 @@ void uml_pm_wake(void) ...@@ -385,6 +385,45 @@ void uml_pm_wake(void)
pm_system_wakeup(); 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) static int init_pm_wake_signal(void)
{ {
/* /*
...@@ -397,6 +436,9 @@ 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) if (time_travel_mode != TT_MODE_EXTERNAL)
register_pm_wake_signal(); register_pm_wake_signal();
suspend_set_ops(&um_suspend_ops);
return 0; return 0;
} }
......
...@@ -234,6 +234,11 @@ void set_handler(int sig) ...@@ -234,6 +234,11 @@ void set_handler(int sig)
panic("sigprocmask failed - errno = %d\n", errno); panic("sigprocmask failed - errno = %d\n", errno);
} }
void send_sigio_to_self(void)
{
kill(os_getpid(), SIGIO);
}
int change_sig(int signal, int on) int change_sig(int signal, int on)
{ {
sigset_t sigset; sigset_t sigset;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment