Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
14c03a4a
Commit
14c03a4a
authored
May 25, 2022
by
Rafael J. Wysocki
Browse files
Options
Browse Files
Download
Plain Diff
Merge back reboot/poweroff notifiers rework for 5.19-rc1.
parents
09583dfe
6779db97
Changes
27
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
639 additions
and
124 deletions
+639
-124
arch/arm/kernel/reboot.c
arch/arm/kernel/reboot.c
+1
-3
arch/arm64/kernel/process.c
arch/arm64/kernel/process.c
+1
-2
arch/csky/kernel/power.c
arch/csky/kernel/power.c
+2
-4
arch/ia64/kernel/process.c
arch/ia64/kernel/process.c
+2
-2
arch/m68k/emu/natfeat.c
arch/m68k/emu/natfeat.c
+2
-1
arch/m68k/include/asm/machdep.h
arch/m68k/include/asm/machdep.h
+0
-1
arch/m68k/kernel/process.c
arch/m68k/kernel/process.c
+2
-3
arch/m68k/kernel/setup_mm.c
arch/m68k/kernel/setup_mm.c
+0
-1
arch/m68k/kernel/setup_no.c
arch/m68k/kernel/setup_no.c
+0
-1
arch/m68k/mac/config.c
arch/m68k/mac/config.c
+3
-1
arch/mips/kernel/reset.c
arch/mips/kernel/reset.c
+1
-2
arch/parisc/kernel/process.c
arch/parisc/kernel/process.c
+2
-2
arch/powerpc/kernel/setup-common.c
arch/powerpc/kernel/setup-common.c
+1
-3
arch/powerpc/xmon/xmon.c
arch/powerpc/xmon/xmon.c
+1
-2
arch/riscv/kernel/reset.c
arch/riscv/kernel/reset.c
+4
-8
arch/sh/kernel/reboot.c
arch/sh/kernel/reboot.c
+1
-2
arch/x86/kernel/reboot.c
arch/x86/kernel/reboot.c
+2
-2
arch/x86/xen/enlighten_pv.c
arch/x86/xen/enlighten_pv.c
+2
-2
drivers/acpi/sleep.c
drivers/acpi/sleep.c
+12
-4
drivers/memory/emif.c
drivers/memory/emif.c
+1
-1
drivers/regulator/pfuze100-regulator.c
drivers/regulator/pfuze100-regulator.c
+17
-25
drivers/soc/tegra/pmc.c
drivers/soc/tegra/pmc.c
+62
-25
include/linux/notifier.h
include/linux/notifier.h
+7
-0
include/linux/pm.h
include/linux/pm.h
+0
-1
include/linux/reboot.h
include/linux/reboot.h
+91
-0
kernel/notifier.c
kernel/notifier.c
+82
-19
kernel/reboot.c
kernel/reboot.c
+340
-7
No files found.
arch/arm/kernel/reboot.c
View file @
14c03a4a
...
...
@@ -116,9 +116,7 @@ void machine_power_off(void)
{
local_irq_disable
();
smp_send_stop
();
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
}
/*
...
...
arch/arm64/kernel/process.c
View file @
14c03a4a
...
...
@@ -111,8 +111,7 @@ void machine_power_off(void)
{
local_irq_disable
();
smp_send_stop
();
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
}
/*
...
...
arch/csky/kernel/power.c
View file @
14c03a4a
...
...
@@ -9,16 +9,14 @@ EXPORT_SYMBOL(pm_power_off);
void
machine_power_off
(
void
)
{
local_irq_disable
();
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
asm
volatile
(
"bkpt"
);
}
void
machine_halt
(
void
)
{
local_irq_disable
();
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
asm
volatile
(
"bkpt"
);
}
...
...
arch/ia64/kernel/process.c
View file @
14c03a4a
...
...
@@ -19,6 +19,7 @@
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/personality.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/sched/hotplug.h>
...
...
@@ -599,8 +600,7 @@ machine_halt (void)
void
machine_power_off
(
void
)
{
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
machine_halt
();
}
...
...
arch/m68k/emu/natfeat.c
View file @
14c03a4a
...
...
@@ -15,6 +15,7 @@
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/io.h>
#include <asm/machdep.h>
#include <asm/natfeat.h>
...
...
@@ -90,5 +91,5 @@ void __init nf_init(void)
pr_info
(
"NatFeats found (%s, %lu.%lu)
\n
"
,
buf
,
version
>>
16
,
version
&
0xffff
);
mach_power_off
=
nf_poweroff
;
register_platform_power_off
(
nf_poweroff
)
;
}
arch/m68k/include/asm/machdep.h
View file @
14c03a4a
...
...
@@ -24,7 +24,6 @@ extern int (*mach_get_rtc_pll)(struct rtc_pll_info *);
extern
int
(
*
mach_set_rtc_pll
)(
struct
rtc_pll_info
*
);
extern
void
(
*
mach_reset
)(
void
);
extern
void
(
*
mach_halt
)(
void
);
extern
void
(
*
mach_power_off
)(
void
);
extern
unsigned
long
(
*
mach_hd_init
)
(
unsigned
long
,
unsigned
long
);
extern
void
(
*
mach_hd_setup
)(
char
*
,
int
*
);
extern
void
(
*
mach_heartbeat
)
(
int
);
...
...
arch/m68k/kernel/process.c
View file @
14c03a4a
...
...
@@ -67,12 +67,11 @@ void machine_halt(void)
void
machine_power_off
(
void
)
{
if
(
mach_power_off
)
mach_power_off
();
do_kernel_power_off
();
for
(;;);
}
void
(
*
pm_power_off
)(
void
)
=
machine_power_off
;
void
(
*
pm_power_off
)(
void
);
EXPORT_SYMBOL
(
pm_power_off
);
void
show_regs
(
struct
pt_regs
*
regs
)
...
...
arch/m68k/kernel/setup_mm.c
View file @
14c03a4a
...
...
@@ -98,7 +98,6 @@ EXPORT_SYMBOL(mach_get_rtc_pll);
EXPORT_SYMBOL
(
mach_set_rtc_pll
);
void
(
*
mach_reset
)(
void
);
void
(
*
mach_halt
)(
void
);
void
(
*
mach_power_off
)(
void
);
#ifdef CONFIG_HEARTBEAT
void
(
*
mach_heartbeat
)
(
int
);
EXPORT_SYMBOL
(
mach_heartbeat
);
...
...
arch/m68k/kernel/setup_no.c
View file @
14c03a4a
...
...
@@ -55,7 +55,6 @@ int (*mach_hwclk) (int, struct rtc_time*);
/* machine dependent reboot functions */
void
(
*
mach_reset
)(
void
);
void
(
*
mach_halt
)(
void
);
void
(
*
mach_power_off
)(
void
);
#ifdef CONFIG_M68000
#if defined(CONFIG_M68328)
...
...
arch/m68k/mac/config.c
View file @
14c03a4a
...
...
@@ -12,6 +12,7 @@
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/tty.h>
...
...
@@ -140,7 +141,6 @@ void __init config_mac(void)
mach_hwclk
=
mac_hwclk
;
mach_reset
=
mac_reset
;
mach_halt
=
mac_poweroff
;
mach_power_off
=
mac_poweroff
;
#if IS_ENABLED(CONFIG_INPUT_M68K_BEEP)
mach_beep
=
mac_mksound
;
#endif
...
...
@@ -160,6 +160,8 @@ void __init config_mac(void)
if
(
macintosh_config
->
ident
==
MAC_MODEL_IICI
)
mach_l2_flush
=
via_l2_flush
;
register_platform_power_off
(
mac_poweroff
);
}
...
...
arch/mips/kernel/reset.c
View file @
14c03a4a
...
...
@@ -114,8 +114,7 @@ void machine_halt(void)
void
machine_power_off
(
void
)
{
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
#ifdef CONFIG_SMP
preempt_disable
();
...
...
arch/parisc/kernel/process.c
View file @
14c03a4a
...
...
@@ -26,6 +26,7 @@
#include <linux/module.h>
#include <linux/personality.h>
#include <linux/ptrace.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/sched/task.h>
...
...
@@ -116,8 +117,7 @@ void machine_power_off(void)
pdc_chassis_send_status
(
PDC_CHASSIS_DIRECT_SHUTDOWN
);
/* ipmi_poweroff may have been installed. */
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
/* It seems we have no way to power the system off via
* software. The user has to press the button himself. */
...
...
arch/powerpc/kernel/setup-common.c
View file @
14c03a4a
...
...
@@ -161,9 +161,7 @@ void machine_restart(char *cmd)
void
machine_power_off
(
void
)
{
machine_shutdown
();
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
smp_send_stop
();
machine_hang
();
}
...
...
arch/powerpc/xmon/xmon.c
View file @
14c03a4a
...
...
@@ -1243,8 +1243,7 @@ static void bootcmds(void)
}
else
if
(
cmd
==
'h'
)
{
ppc_md
.
halt
();
}
else
if
(
cmd
==
'p'
)
{
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
}
}
...
...
arch/riscv/kernel/reset.c
View file @
14c03a4a
...
...
@@ -23,16 +23,12 @@ void machine_restart(char *cmd)
void
machine_halt
(
void
)
{
if
(
pm_power_off
!=
NULL
)
pm_power_off
();
else
do_kernel_power_off
();
default_power_off
();
}
void
machine_power_off
(
void
)
{
if
(
pm_power_off
!=
NULL
)
pm_power_off
();
else
do_kernel_power_off
();
default_power_off
();
}
arch/sh/kernel/reboot.c
View file @
14c03a4a
...
...
@@ -46,8 +46,7 @@ static void native_machine_shutdown(void)
static
void
native_machine_power_off
(
void
)
{
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
}
static
void
native_machine_halt
(
void
)
...
...
arch/x86/kernel/reboot.c
View file @
14c03a4a
...
...
@@ -739,10 +739,10 @@ static void native_machine_halt(void)
static
void
native_machine_power_off
(
void
)
{
if
(
pm_power_off
)
{
if
(
kernel_can_power_off
()
)
{
if
(
!
reboot_force
)
machine_shutdown
();
pm
_power_off
();
do_kernel
_power_off
();
}
/* A fallback in case there is no PM info available */
tboot_shutdown
(
TB_SHUTDOWN_HALT
);
...
...
arch/x86/xen/enlighten_pv.c
View file @
14c03a4a
...
...
@@ -30,6 +30,7 @@
#include <linux/pci.h>
#include <linux/gfp.h>
#include <linux/edd.h>
#include <linux/reboot.h>
#include <xen/xen.h>
#include <xen/events.h>
...
...
@@ -1069,8 +1070,7 @@ static void xen_machine_halt(void)
static
void
xen_machine_power_off
(
void
)
{
if
(
pm_power_off
)
pm_power_off
();
do_kernel_power_off
();
xen_reboot
(
SHUTDOWN_poweroff
);
}
...
...
drivers/acpi/sleep.c
View file @
14c03a4a
...
...
@@ -1035,20 +1035,22 @@ static void acpi_sleep_hibernate_setup(void)
static
inline
void
acpi_sleep_hibernate_setup
(
void
)
{}
#endif
/* !CONFIG_HIBERNATION */
static
void
acpi_power_off_prepare
(
void
)
static
int
acpi_power_off_prepare
(
struct
sys_off_data
*
data
)
{
/* Prepare to power off the system */
acpi_sleep_prepare
(
ACPI_STATE_S5
);
acpi_disable_all_gpes
();
acpi_os_wait_events_complete
();
return
NOTIFY_DONE
;
}
static
void
acpi_power_off
(
void
)
static
int
acpi_power_off
(
struct
sys_off_data
*
data
)
{
/* acpi_sleep_prepare(ACPI_STATE_S5) should have already been called */
pr_debug
(
"%s called
\n
"
,
__func__
);
local_irq_disable
();
acpi_enter_sleep_state
(
ACPI_STATE_S5
);
return
NOTIFY_DONE
;
}
int
__init
acpi_sleep_init
(
void
)
...
...
@@ -1067,8 +1069,14 @@ int __init acpi_sleep_init(void)
if
(
acpi_sleep_state_supported
(
ACPI_STATE_S5
))
{
sleep_states
[
ACPI_STATE_S5
]
=
1
;
pm_power_off_prepare
=
acpi_power_off_prepare
;
pm_power_off
=
acpi_power_off
;
register_sys_off_handler
(
SYS_OFF_MODE_POWER_OFF_PREPARE
,
SYS_OFF_PRIO_FIRMWARE
,
acpi_power_off_prepare
,
NULL
);
register_sys_off_handler
(
SYS_OFF_MODE_POWER_OFF
,
SYS_OFF_PRIO_FIRMWARE
,
acpi_power_off
,
NULL
);
}
else
{
acpi_no_s5
=
true
;
}
...
...
drivers/memory/emif.c
View file @
14c03a4a
...
...
@@ -630,7 +630,7 @@ static irqreturn_t emif_threaded_isr(int irq, void *dev_id)
dev_emerg
(
emif
->
dev
,
"SDRAM temperature exceeds operating limit.. Needs shut down!!!
\n
"
);
/* If we have Power OFF ability, use it, else try restarting */
if
(
pm_power_off
)
{
if
(
kernel_can_power_off
()
)
{
kernel_power_off
();
}
else
{
WARN
(
1
,
"FIXME: NO pm_power_off!!! trying restart
\n
"
);
...
...
drivers/regulator/pfuze100-regulator.c
View file @
14c03a4a
...
...
@@ -10,6 +10,7 @@
#include <linux/of_device.h>
#include <linux/regulator/of_regulator.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/pfuze100.h>
...
...
@@ -571,10 +572,10 @@ static inline struct device_node *match_of_node(int index)
return
pfuze_matches
[
index
].
of_node
;
}
static
struct
pfuze_chip
*
syspm_pfuze_chip
;
static
void
pfuze_power_off_prepare
(
void
)
static
int
pfuze_power_off_prepare
(
struct
sys_off_data
*
data
)
{
struct
pfuze_chip
*
syspm_pfuze_chip
=
data
->
cb_data
;
dev_info
(
syspm_pfuze_chip
->
dev
,
"Configure standby mode for power off"
);
/* Switch from default mode: APS/APS to APS/Off */
...
...
@@ -609,28 +610,30 @@ static void pfuze_power_off_prepare(void)
regmap_update_bits
(
syspm_pfuze_chip
->
regmap
,
PFUZE100_VGEN6VOL
,
PFUZE100_VGENxLPWR
|
PFUZE100_VGENxSTBY
,
PFUZE100_VGENxSTBY
);
return
NOTIFY_DONE
;
}
static
int
pfuze_power_off_prepare_init
(
struct
pfuze_chip
*
pfuze_chip
)
{
int
err
;
if
(
pfuze_chip
->
chip_id
!=
PFUZE100
)
{
dev_warn
(
pfuze_chip
->
dev
,
"Requested pm_power_off_prepare handler for not supported chip
\n
"
);
return
-
ENODEV
;
}
if
(
pm_power_off_prepare
)
{
dev_warn
(
pfuze_chip
->
dev
,
"pm_power_off_prepare is already registered.
\n
"
);
return
-
EBUSY
;
err
=
devm_register_sys_off_handler
(
pfuze_chip
->
dev
,
SYS_OFF_MODE_POWER_OFF_PREPARE
,
SYS_OFF_PRIO_DEFAULT
,
pfuze_power_off_prepare
,
pfuze_chip
);
if
(
err
)
{
dev_err
(
pfuze_chip
->
dev
,
"failed to register sys-off handler: %d
\n
"
,
err
);
return
err
;
}
if
(
syspm_pfuze_chip
)
{
dev_warn
(
pfuze_chip
->
dev
,
"syspm_pfuze_chip is already set.
\n
"
);
return
-
EBUSY
;
}
syspm_pfuze_chip
=
pfuze_chip
;
pm_power_off_prepare
=
pfuze_power_off_prepare
;
return
0
;
}
...
...
@@ -839,23 +842,12 @@ static int pfuze100_regulator_probe(struct i2c_client *client,
return
0
;
}
static
int
pfuze100_regulator_remove
(
struct
i2c_client
*
client
)
{
if
(
syspm_pfuze_chip
)
{
syspm_pfuze_chip
=
NULL
;
pm_power_off_prepare
=
NULL
;
}
return
0
;
}
static
struct
i2c_driver
pfuze_driver
=
{
.
driver
=
{
.
name
=
"pfuze100-regulator"
,
.
of_match_table
=
pfuze_dt_ids
,
},
.
probe
=
pfuze100_regulator_probe
,
.
remove
=
pfuze100_regulator_remove
,
};
module_i2c_driver
(
pfuze_driver
);
...
...
drivers/soc/tegra/pmc.c
View file @
14c03a4a
...
...
@@ -39,6 +39,7 @@
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/reset.h>
...
...
@@ -108,6 +109,7 @@
#define PMC_USB_DEBOUNCE_DEL 0xec
#define PMC_USB_AO 0xf0
#define PMC_SCRATCH37 0x130
#define PMC_SCRATCH41 0x140
#define PMC_WAKE2_MASK 0x160
...
...
@@ -1099,8 +1101,7 @@ static struct notifier_block tegra_pmc_reboot_notifier = {
.
notifier_call
=
tegra_pmc_reboot_notify
,
};
static
int
tegra_pmc_restart_notify
(
struct
notifier_block
*
this
,
unsigned
long
action
,
void
*
data
)
static
void
tegra_pmc_restart
(
void
)
{
u32
value
;
...
...
@@ -1108,14 +1109,31 @@ static int tegra_pmc_restart_notify(struct notifier_block *this,
value
=
tegra_pmc_readl
(
pmc
,
PMC_CNTRL
);
value
|=
PMC_CNTRL_MAIN_RST
;
tegra_pmc_writel
(
pmc
,
value
,
PMC_CNTRL
);
}
static
int
tegra_pmc_restart_handler
(
struct
sys_off_data
*
data
)
{
tegra_pmc_restart
();
return
NOTIFY_DONE
;
}
static
struct
notifier_block
tegra_pmc_restart_handler
=
{
.
notifier_call
=
tegra_pmc_restart_notify
,
.
priority
=
128
,
};
static
int
tegra_pmc_power_off_handler
(
struct
sys_off_data
*
data
)
{
/*
* Reboot Nexus 7 into special bootloader mode if USB cable is
* connected in order to display battery status and power off.
*/
if
(
of_machine_is_compatible
(
"asus,grouper"
)
&&
power_supply_is_system_supplied
())
{
const
u32
go_to_charger_mode
=
0xa5a55a5a
;
tegra_pmc_writel
(
pmc
,
go_to_charger_mode
,
PMC_SCRATCH37
);
tegra_pmc_restart
();
}
return
NOTIFY_DONE
;
}
static
int
powergate_show
(
struct
seq_file
*
s
,
void
*
data
)
{
...
...
@@ -2877,6 +2895,42 @@ static int tegra_pmc_probe(struct platform_device *pdev)
pmc
->
clk
=
NULL
;
}
/*
* PMC should be last resort for restarting since it soft-resets
* CPU without resetting everything else.
*/
err
=
devm_register_reboot_notifier
(
&
pdev
->
dev
,
&
tegra_pmc_reboot_notifier
);
if
(
err
)
{
dev_err
(
&
pdev
->
dev
,
"unable to register reboot notifier, %d
\n
"
,
err
);
return
err
;
}
err
=
devm_register_sys_off_handler
(
&
pdev
->
dev
,
SYS_OFF_MODE_RESTART
,
SYS_OFF_PRIO_LOW
,
tegra_pmc_restart_handler
,
NULL
);
if
(
err
)
{
dev_err
(
&
pdev
->
dev
,
"failed to register sys-off handler: %d
\n
"
,
err
);
return
err
;
}
/*
* PMC should be primary power-off method if it soft-resets CPU,
* asking bootloader to shutdown hardware.
*/
err
=
devm_register_sys_off_handler
(
&
pdev
->
dev
,
SYS_OFF_MODE_POWER_OFF
,
SYS_OFF_PRIO_FIRMWARE
,
tegra_pmc_power_off_handler
,
NULL
);
if
(
err
)
{
dev_err
(
&
pdev
->
dev
,
"failed to register sys-off handler: %d
\n
"
,
err
);
return
err
;
}
/*
* PCLK clock rate can't be retrieved using CLK API because it
* causes lockup if CPU enters LP2 idle state from some other
...
...
@@ -2908,28 +2962,13 @@ static int tegra_pmc_probe(struct platform_device *pdev)
goto
cleanup_sysfs
;
}
err
=
devm_register_reboot_notifier
(
&
pdev
->
dev
,
&
tegra_pmc_reboot_notifier
);
if
(
err
)
{
dev_err
(
&
pdev
->
dev
,
"unable to register reboot notifier, %d
\n
"
,
err
);
goto
cleanup_debugfs
;
}
err
=
register_restart_handler
(
&
tegra_pmc_restart_handler
);
if
(
err
)
{
dev_err
(
&
pdev
->
dev
,
"unable to register restart handler, %d
\n
"
,
err
);
goto
cleanup_debugfs
;
}
err
=
tegra_pmc_pinctrl_init
(
pmc
);
if
(
err
)
goto
cleanup_
restart_handler
;
goto
cleanup_
debugfs
;
err
=
tegra_pmc_regmap_init
(
pmc
);
if
(
err
<
0
)
goto
cleanup_
restart_handler
;
goto
cleanup_
debugfs
;
err
=
tegra_powergate_init
(
pmc
,
pdev
->
dev
.
of_node
);
if
(
err
<
0
)
...
...
@@ -2952,8 +2991,6 @@ static int tegra_pmc_probe(struct platform_device *pdev)
cleanup_powergates:
tegra_powergate_remove_all
(
pdev
->
dev
.
of_node
);
cleanup_restart_handler:
unregister_restart_handler
(
&
tegra_pmc_restart_handler
);
cleanup_debugfs:
debugfs_remove
(
pmc
->
debugfs
);
cleanup_sysfs:
...
...
include/linux/notifier.h
View file @
14c03a4a
...
...
@@ -150,6 +150,11 @@ extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
extern
int
srcu_notifier_chain_register
(
struct
srcu_notifier_head
*
nh
,
struct
notifier_block
*
nb
);
extern
int
atomic_notifier_chain_register_unique_prio
(
struct
atomic_notifier_head
*
nh
,
struct
notifier_block
*
nb
);
extern
int
blocking_notifier_chain_register_unique_prio
(
struct
blocking_notifier_head
*
nh
,
struct
notifier_block
*
nb
);
extern
int
atomic_notifier_chain_unregister
(
struct
atomic_notifier_head
*
nh
,
struct
notifier_block
*
nb
);
extern
int
blocking_notifier_chain_unregister
(
struct
blocking_notifier_head
*
nh
,
...
...
@@ -173,6 +178,8 @@ extern int blocking_notifier_call_chain_robust(struct blocking_notifier_head *nh
extern
int
raw_notifier_call_chain_robust
(
struct
raw_notifier_head
*
nh
,
unsigned
long
val_up
,
unsigned
long
val_down
,
void
*
v
);
extern
bool
atomic_notifier_call_chain_is_empty
(
struct
atomic_notifier_head
*
nh
);
#define NOTIFY_DONE 0x0000
/* Don't care */
#define NOTIFY_OK 0x0001
/* Suits me */
#define NOTIFY_STOP_MASK 0x8000
/* Don't call further */
...
...
include/linux/pm.h
View file @
14c03a4a
...
...
@@ -21,7 +21,6 @@
* Callbacks for platform drivers to implement.
*/
extern
void
(
*
pm_power_off
)(
void
);
extern
void
(
*
pm_power_off_prepare
)(
void
);
struct
device
;
/* we have a circular dep with device.h */
#ifdef CONFIG_VT_CONSOLE_SLEEP
...
...
include/linux/reboot.h
View file @
14c03a4a
...
...
@@ -7,6 +7,7 @@
#include <uapi/linux/reboot.h>
struct
device
;
struct
sys_off_handler
;
#define SYS_DOWN 0x0001
/* Notify of system down */
#define SYS_RESTART SYS_DOWN
...
...
@@ -62,6 +63,95 @@ extern void machine_shutdown(void);
struct
pt_regs
;
extern
void
machine_crash_shutdown
(
struct
pt_regs
*
);
void
do_kernel_power_off
(
void
);
/*
* sys-off handler API.
*/
/*
* Standard sys-off priority levels. Users are expected to set priorities
* relative to the standard levels.
*
* SYS_OFF_PRIO_PLATFORM: Use this for platform-level handlers.
*
* SYS_OFF_PRIO_LOW: Use this for handler of last resort.
*
* SYS_OFF_PRIO_DEFAULT: Use this for normal handlers.
*
* SYS_OFF_PRIO_HIGH: Use this for higher priority handlers.
*
* SYS_OFF_PRIO_FIRMWARE: Use this if handler uses firmware call.
*/
#define SYS_OFF_PRIO_PLATFORM -256
#define SYS_OFF_PRIO_LOW -128
#define SYS_OFF_PRIO_DEFAULT 0
#define SYS_OFF_PRIO_HIGH 192
#define SYS_OFF_PRIO_FIRMWARE 224
enum
sys_off_mode
{
/**
* @SYS_OFF_MODE_POWER_OFF_PREPARE:
*
* Handlers prepare system to be powered off. Handlers are
* allowed to sleep.
*/
SYS_OFF_MODE_POWER_OFF_PREPARE
,
/**
* @SYS_OFF_MODE_POWER_OFF:
*
* Handlers power-off system. Handlers are disallowed to sleep.
*/
SYS_OFF_MODE_POWER_OFF
,
/**
* @SYS_OFF_MODE_RESTART:
*
* Handlers restart system. Handlers are disallowed to sleep.
*/
SYS_OFF_MODE_RESTART
,
};
/**
* struct sys_off_data - sys-off callback argument
*
* @mode: Mode ID. Currently used only by the sys-off restart mode,
* see enum reboot_mode for the available modes.
* @cb_data: User's callback data.
* @cmd: Command string. Currently used only by the sys-off restart mode,
* NULL otherwise.
*/
struct
sys_off_data
{
int
mode
;
void
*
cb_data
;
const
char
*
cmd
;
};
struct
sys_off_handler
*
register_sys_off_handler
(
enum
sys_off_mode
mode
,
int
priority
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
);
void
unregister_sys_off_handler
(
struct
sys_off_handler
*
handler
);
int
devm_register_sys_off_handler
(
struct
device
*
dev
,
enum
sys_off_mode
mode
,
int
priority
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
);
int
devm_register_power_off_handler
(
struct
device
*
dev
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
);
int
devm_register_restart_handler
(
struct
device
*
dev
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
);
int
register_platform_power_off
(
void
(
*
power_off
)(
void
));
void
unregister_platform_power_off
(
void
(
*
power_off
)(
void
));
/*
* Architecture independent implemenations of sys_reboot commands.
*/
...
...
@@ -70,6 +160,7 @@ extern void kernel_restart_prepare(char *cmd);
extern
void
kernel_restart
(
char
*
cmd
);
extern
void
kernel_halt
(
void
);
extern
void
kernel_power_off
(
void
);
extern
bool
kernel_can_power_off
(
void
);
extern
int
C_A_D
;
/* for sysctl */
void
ctrl_alt_del
(
void
);
...
...
kernel/notifier.c
View file @
14c03a4a
...
...
@@ -20,7 +20,8 @@ BLOCKING_NOTIFIER_HEAD(reboot_notifier_list);
*/
static
int
notifier_chain_register
(
struct
notifier_block
**
nl
,
struct
notifier_block
*
n
)
struct
notifier_block
*
n
,
bool
unique_priority
)
{
while
((
*
nl
)
!=
NULL
)
{
if
(
unlikely
((
*
nl
)
==
n
))
{
...
...
@@ -30,6 +31,8 @@ static int notifier_chain_register(struct notifier_block **nl,
}
if
(
n
->
priority
>
(
*
nl
)
->
priority
)
break
;
if
(
n
->
priority
==
(
*
nl
)
->
priority
&&
unique_priority
)
return
-
EBUSY
;
nl
=
&
((
*
nl
)
->
next
);
}
n
->
next
=
*
nl
;
...
...
@@ -144,12 +147,35 @@ int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
int
ret
;
spin_lock_irqsave
(
&
nh
->
lock
,
flags
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
,
false
);
spin_unlock_irqrestore
(
&
nh
->
lock
,
flags
);
return
ret
;
}
EXPORT_SYMBOL_GPL
(
atomic_notifier_chain_register
);
/**
* atomic_notifier_chain_register_unique_prio - Add notifier to an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain
* @n: New entry in notifier chain
*
* Adds a notifier to an atomic notifier chain if there is no other
* notifier registered using the same priority.
*
* Returns 0 on success, %-EEXIST or %-EBUSY on error.
*/
int
atomic_notifier_chain_register_unique_prio
(
struct
atomic_notifier_head
*
nh
,
struct
notifier_block
*
n
)
{
unsigned
long
flags
;
int
ret
;
spin_lock_irqsave
(
&
nh
->
lock
,
flags
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
,
true
);
spin_unlock_irqrestore
(
&
nh
->
lock
,
flags
);
return
ret
;
}
EXPORT_SYMBOL_GPL
(
atomic_notifier_chain_register_unique_prio
);
/**
* atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain
...
...
@@ -204,11 +230,44 @@ int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
EXPORT_SYMBOL_GPL
(
atomic_notifier_call_chain
);
NOKPROBE_SYMBOL
(
atomic_notifier_call_chain
);
/**
* atomic_notifier_call_chain_is_empty - Check whether notifier chain is empty
* @nh: Pointer to head of the atomic notifier chain
*
* Checks whether notifier chain is empty.
*
* Returns true is notifier chain is empty, false otherwise.
*/
bool
atomic_notifier_call_chain_is_empty
(
struct
atomic_notifier_head
*
nh
)
{
return
!
rcu_access_pointer
(
nh
->
head
);
}
/*
* Blocking notifier chain routines. All access to the chain is
* synchronized by an rwsem.
*/
static
int
__blocking_notifier_chain_register
(
struct
blocking_notifier_head
*
nh
,
struct
notifier_block
*
n
,
bool
unique_priority
)
{
int
ret
;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
if
(
unlikely
(
system_state
==
SYSTEM_BOOTING
))
return
notifier_chain_register
(
&
nh
->
head
,
n
,
unique_priority
);
down_write
(
&
nh
->
rwsem
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
,
unique_priority
);
up_write
(
&
nh
->
rwsem
);
return
ret
;
}
/**
* blocking_notifier_chain_register - Add notifier to a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain
...
...
@@ -222,22 +281,26 @@ NOKPROBE_SYMBOL(atomic_notifier_call_chain);
int
blocking_notifier_chain_register
(
struct
blocking_notifier_head
*
nh
,
struct
notifier_block
*
n
)
{
int
ret
;
return
__blocking_notifier_chain_register
(
nh
,
n
,
false
);
}
EXPORT_SYMBOL_GPL
(
blocking_notifier_chain_register
);
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
/**
* blocking_notifier_chain_register_unique_prio - Add notifier to a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain
* @n: New entry in notifier chain
*
* Adds a notifier to an blocking notifier chain if there is no other
* notifier registered using the same priority.
*
* Returns 0 on success, %-EEXIST or %-EBUSY on error.
*/
if
(
unlikely
(
system_state
==
SYSTEM_BOOTING
))
return
notifier_chain_register
(
&
nh
->
head
,
n
);
down_write
(
&
nh
->
rwsem
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
);
up_write
(
&
nh
->
rwsem
);
return
ret
;
int
blocking_notifier_chain_register_unique_prio
(
struct
blocking_notifier_head
*
nh
,
struct
notifier_block
*
n
)
{
return
__blocking_notifier_chain_register
(
nh
,
n
,
true
);
}
EXPORT_SYMBOL_GPL
(
blocking_notifier_chain_register
);
EXPORT_SYMBOL_GPL
(
blocking_notifier_chain_register
_unique_prio
);
/**
* blocking_notifier_chain_unregister - Remove notifier from a blocking notifier chain
...
...
@@ -341,7 +404,7 @@ EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);
int
raw_notifier_chain_register
(
struct
raw_notifier_head
*
nh
,
struct
notifier_block
*
n
)
{
return
notifier_chain_register
(
&
nh
->
head
,
n
);
return
notifier_chain_register
(
&
nh
->
head
,
n
,
false
);
}
EXPORT_SYMBOL_GPL
(
raw_notifier_chain_register
);
...
...
@@ -420,10 +483,10 @@ int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
* such times we must not call mutex_lock().
*/
if
(
unlikely
(
system_state
==
SYSTEM_BOOTING
))
return
notifier_chain_register
(
&
nh
->
head
,
n
);
return
notifier_chain_register
(
&
nh
->
head
,
n
,
false
);
mutex_lock
(
&
nh
->
mutex
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
);
ret
=
notifier_chain_register
(
&
nh
->
head
,
n
,
false
);
mutex_unlock
(
&
nh
->
mutex
);
return
ret
;
}
...
...
kernel/reboot.c
View file @
14c03a4a
...
...
@@ -48,12 +48,20 @@ int reboot_cpu;
enum
reboot_type
reboot_type
=
BOOT_ACPI
;
int
reboot_force
;
struct
sys_off_handler
{
struct
notifier_block
nb
;
int
(
*
sys_off_cb
)(
struct
sys_off_data
*
data
);
void
*
cb_data
;
enum
sys_off_mode
mode
;
bool
blocking
;
void
*
list
;
};
/*
* If set, this is used for preparing the system to power off.
* Temporary stub that prevents linkage failure while we're in process
* of removing all uses of legacy pm_power_off() around the kernel.
*/
void
(
*
pm_power_off_prepare
)(
void
);
EXPORT_SYMBOL_GPL
(
pm_power_off_prepare
);
void
__weak
(
*
pm_power_off
)(
void
);
/**
* emergency_restart - reboot the system
...
...
@@ -281,6 +289,332 @@ void kernel_halt(void)
}
EXPORT_SYMBOL_GPL
(
kernel_halt
);
/*
* Notifier list for kernel code which wants to be called
* to prepare system for power off.
*/
static
BLOCKING_NOTIFIER_HEAD
(
power_off_prep_handler_list
);
/*
* Notifier list for kernel code which wants to be called
* to power off system.
*/
static
ATOMIC_NOTIFIER_HEAD
(
power_off_handler_list
);
static
int
sys_off_notify
(
struct
notifier_block
*
nb
,
unsigned
long
mode
,
void
*
cmd
)
{
struct
sys_off_handler
*
handler
;
struct
sys_off_data
data
=
{};
handler
=
container_of
(
nb
,
struct
sys_off_handler
,
nb
);
data
.
cb_data
=
handler
->
cb_data
;
data
.
mode
=
mode
;
data
.
cmd
=
cmd
;
return
handler
->
sys_off_cb
(
&
data
);
}
/**
* register_sys_off_handler - Register sys-off handler
* @mode: Sys-off mode
* @priority: Handler priority
* @callback: Callback function
* @cb_data: Callback argument
*
* Registers system power-off or restart handler that will be invoked
* at the step corresponding to the given sys-off mode. Handler's callback
* should return NOTIFY_DONE to permit execution of the next handler in
* the call chain or NOTIFY_STOP to break the chain (in error case for
* example).
*
* Multiple handlers can be registered at the default priority level.
*
* Only one handler can be registered at the non-default priority level,
* otherwise ERR_PTR(-EBUSY) is returned.
*
* Returns a new instance of struct sys_off_handler on success, or
* an ERR_PTR()-encoded error code otherwise.
*/
struct
sys_off_handler
*
register_sys_off_handler
(
enum
sys_off_mode
mode
,
int
priority
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
)
{
struct
sys_off_handler
*
handler
;
int
err
;
handler
=
kzalloc
(
sizeof
(
*
handler
),
GFP_KERNEL
);
if
(
!
handler
)
return
ERR_PTR
(
-
ENOMEM
);
switch
(
mode
)
{
case
SYS_OFF_MODE_POWER_OFF_PREPARE
:
handler
->
list
=
&
power_off_prep_handler_list
;
handler
->
blocking
=
true
;
break
;
case
SYS_OFF_MODE_POWER_OFF
:
handler
->
list
=
&
power_off_handler_list
;
break
;
case
SYS_OFF_MODE_RESTART
:
handler
->
list
=
&
restart_handler_list
;
break
;
default:
kfree
(
handler
);
return
ERR_PTR
(
-
EINVAL
);
}
handler
->
nb
.
notifier_call
=
sys_off_notify
;
handler
->
nb
.
priority
=
priority
;
handler
->
sys_off_cb
=
callback
;
handler
->
cb_data
=
cb_data
;
handler
->
mode
=
mode
;
if
(
handler
->
blocking
)
{
if
(
priority
==
SYS_OFF_PRIO_DEFAULT
)
err
=
blocking_notifier_chain_register
(
handler
->
list
,
&
handler
->
nb
);
else
err
=
blocking_notifier_chain_register_unique_prio
(
handler
->
list
,
&
handler
->
nb
);
}
else
{
if
(
priority
==
SYS_OFF_PRIO_DEFAULT
)
err
=
atomic_notifier_chain_register
(
handler
->
list
,
&
handler
->
nb
);
else
err
=
atomic_notifier_chain_register_unique_prio
(
handler
->
list
,
&
handler
->
nb
);
}
if
(
err
)
{
kfree
(
handler
);
return
ERR_PTR
(
err
);
}
return
handler
;
}
EXPORT_SYMBOL_GPL
(
register_sys_off_handler
);
/**
* unregister_sys_off_handler - Unregister sys-off handler
* @handler: Sys-off handler
*
* Unregisters given sys-off handler.
*/
void
unregister_sys_off_handler
(
struct
sys_off_handler
*
handler
)
{
int
err
;
if
(
!
handler
)
return
;
if
(
handler
->
blocking
)
err
=
blocking_notifier_chain_unregister
(
handler
->
list
,
&
handler
->
nb
);
else
err
=
atomic_notifier_chain_unregister
(
handler
->
list
,
&
handler
->
nb
);
/* sanity check, shall never happen */
WARN_ON
(
err
);
kfree
(
handler
);
}
EXPORT_SYMBOL_GPL
(
unregister_sys_off_handler
);
static
void
devm_unregister_sys_off_handler
(
void
*
data
)
{
struct
sys_off_handler
*
handler
=
data
;
unregister_sys_off_handler
(
handler
);
}
/**
* devm_register_sys_off_handler - Register sys-off handler
* @dev: Device that registers handler
* @mode: Sys-off mode
* @priority: Handler priority
* @callback: Callback function
* @cb_data: Callback argument
*
* Registers resource-managed sys-off handler.
*
* Returns zero on success, or error code on failure.
*/
int
devm_register_sys_off_handler
(
struct
device
*
dev
,
enum
sys_off_mode
mode
,
int
priority
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
)
{
struct
sys_off_handler
*
handler
;
handler
=
register_sys_off_handler
(
mode
,
priority
,
callback
,
cb_data
);
if
(
IS_ERR
(
handler
))
return
PTR_ERR
(
handler
);
return
devm_add_action_or_reset
(
dev
,
devm_unregister_sys_off_handler
,
handler
);
}
EXPORT_SYMBOL_GPL
(
devm_register_sys_off_handler
);
/**
* devm_register_power_off_handler - Register power-off handler
* @dev: Device that registers callback
* @callback: Callback function
* @cb_data: Callback's argument
*
* Registers resource-managed sys-off handler with a default priority
* and using power-off mode.
*
* Returns zero on success, or error code on failure.
*/
int
devm_register_power_off_handler
(
struct
device
*
dev
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
)
{
return
devm_register_sys_off_handler
(
dev
,
SYS_OFF_MODE_POWER_OFF
,
SYS_OFF_PRIO_DEFAULT
,
callback
,
cb_data
);
}
EXPORT_SYMBOL_GPL
(
devm_register_power_off_handler
);
/**
* devm_register_restart_handler - Register restart handler
* @dev: Device that registers callback
* @callback: Callback function
* @cb_data: Callback's argument
*
* Registers resource-managed sys-off handler with a default priority
* and using restart mode.
*
* Returns zero on success, or error code on failure.
*/
int
devm_register_restart_handler
(
struct
device
*
dev
,
int
(
*
callback
)(
struct
sys_off_data
*
data
),
void
*
cb_data
)
{
return
devm_register_sys_off_handler
(
dev
,
SYS_OFF_MODE_RESTART
,
SYS_OFF_PRIO_DEFAULT
,
callback
,
cb_data
);
}
EXPORT_SYMBOL_GPL
(
devm_register_restart_handler
);
static
struct
sys_off_handler
*
platform_power_off_handler
;
static
int
platform_power_off_notify
(
struct
sys_off_data
*
data
)
{
void
(
*
platform_power_power_off_cb
)(
void
)
=
data
->
cb_data
;
platform_power_power_off_cb
();
return
NOTIFY_DONE
;
}
/**
* register_platform_power_off - Register platform-level power-off callback
* @power_off: Power-off callback
*
* Registers power-off callback that will be called as last step
* of the power-off sequence. This callback is expected to be invoked
* for the last resort. Only one platform power-off callback is allowed
* to be registered at a time.
*
* Returns zero on success, or error code on failure.
*/
int
register_platform_power_off
(
void
(
*
power_off
)(
void
))
{
struct
sys_off_handler
*
handler
;
handler
=
register_sys_off_handler
(
SYS_OFF_MODE_POWER_OFF
,
SYS_OFF_PRIO_PLATFORM
,
platform_power_off_notify
,
power_off
);
if
(
IS_ERR
(
handler
))
return
PTR_ERR
(
handler
);
platform_power_off_handler
=
handler
;
return
0
;
}
EXPORT_SYMBOL_GPL
(
register_platform_power_off
);
/**
* unregister_platform_power_off - Unregister platform-level power-off callback
* @power_off: Power-off callback
*
* Unregisters previously registered platform power-off callback.
*/
void
unregister_platform_power_off
(
void
(
*
power_off
)(
void
))
{
if
(
platform_power_off_handler
&&
platform_power_off_handler
->
cb_data
==
power_off
)
{
unregister_sys_off_handler
(
platform_power_off_handler
);
platform_power_off_handler
=
NULL
;
}
}
EXPORT_SYMBOL_GPL
(
unregister_platform_power_off
);
static
int
legacy_pm_power_off
(
struct
sys_off_data
*
data
)
{
if
(
pm_power_off
)
pm_power_off
();
return
NOTIFY_DONE
;
}
/*
* Register sys-off handlers for legacy PM callbacks. This allows legacy
* PM callbacks co-exist with the new sys-off API.
*
* TODO: Remove legacy handlers once all legacy PM users will be switched
* to the sys-off based APIs.
*/
static
int
__init
legacy_pm_init
(
void
)
{
register_sys_off_handler
(
SYS_OFF_MODE_POWER_OFF
,
SYS_OFF_PRIO_DEFAULT
,
legacy_pm_power_off
,
NULL
);
return
0
;
}
core_initcall
(
legacy_pm_init
);
static
void
do_kernel_power_off_prepare
(
void
)
{
blocking_notifier_call_chain
(
&
power_off_prep_handler_list
,
0
,
NULL
);
}
/**
* do_kernel_power_off - Execute kernel power-off handler call chain
*
* Expected to be called as last step of the power-off sequence.
*
* Powers off the system immediately if a power-off handler function has
* been registered. Otherwise does nothing.
*/
void
do_kernel_power_off
(
void
)
{
atomic_notifier_call_chain
(
&
power_off_handler_list
,
0
,
NULL
);
}
/**
* kernel_can_power_off - check whether system can be powered off
*
* Returns true if power-off handler is registered and system can be
* powered off, false otherwise.
*/
bool
kernel_can_power_off
(
void
)
{
return
!
atomic_notifier_call_chain_is_empty
(
&
power_off_handler_list
);
}
EXPORT_SYMBOL_GPL
(
kernel_can_power_off
);
/**
* kernel_power_off - power_off the system
*
...
...
@@ -289,8 +623,7 @@ EXPORT_SYMBOL_GPL(kernel_halt);
void
kernel_power_off
(
void
)
{
kernel_shutdown_prepare
(
SYSTEM_POWER_OFF
);
if
(
pm_power_off_prepare
)
pm_power_off_prepare
();
do_kernel_power_off_prepare
();
migrate_to_reboot_cpu
();
syscore_shutdown
();
pr_emerg
(
"Power down
\n
"
);
...
...
@@ -340,7 +673,7 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
/* Instead of trying to make the power_off code look like
* halt when pm_power_off is not set do it the easy way.
*/
if
((
cmd
==
LINUX_REBOOT_CMD_POWER_OFF
)
&&
!
pm_power_off
)
if
((
cmd
==
LINUX_REBOOT_CMD_POWER_OFF
)
&&
!
kernel_can_power_off
()
)
cmd
=
LINUX_REBOOT_CMD_HALT
;
mutex_lock
(
&
system_transition_mutex
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment