Problem
-------
SoC devices require power-off call chaining functionality from kernel.
We have a widely used restart chaining provided by restart notifier API,
but nothing for power-off.
Solution
--------
Introduce new API that provides both restart and power-off call chains.
Why combine restart with power-off? Because drivers often do both.
More practical to have API that provides both under the same roof.
The new API is designed with simplicity and extensibility in mind.
It's built upon the existing restart and reboot APIs. The simplicity
is in new helper functions that are convenient for drivers. The
extensibility is in the design that doesn't hardcode callback
arguments, making easy to add new parameters and remove old.
This is a third attempt to introduce the new API. First was made by
Guenter Roeck back in 2014, second was made by Thierry Reding in 2017.
In fact the work didn't stop and recently arm_pm_restart() was removed
from v5.14 kernel, which was a part of preparatory work started by
Guenter Roeck. I took into account experience and ideas from the
previous attempts, extended and polished them.
Adoption plan
-------------
This patchset introduces the new API. It also converts multiple drivers
and arch code to the new API to demonstrate how it all looks in practice.
The plan is:
1. Merge new API (patches 1-8). This API will co-exist with the old APIs.
2. Convert arch code to do_kernel_power_off() (patches 9-21).
3. Convert drivers and platform code to the new API.
4. Remove obsolete pm_power_off and pm_power_off_prepare variables.
5. Make restart-notifier API private to kernel/reboot.c once no users left.
It's fully implemented here:
[1] https://github.com/grate-driver/linux/commits/sys-off-handler
For now I'm sending only the first 25 base patches out of ~180. It's
preferable to squash 1-2, partially 3 and 4 points of the plan into a
single patchset to ease and speed up applying of the rest of the patches.
Majority of drivers and platform patches depend on the base, hence they
will come later (and per subsystem), once base will land.
All [1] patches are compile-tested. Tegra and x86 ACPI patches are tested
on hardware. The remaining should be covered by unit tests (unpublished).
Results
-------
1. Devices can be powered off properly.
2. Global variables are removed from drivers.
3. Global pm_power_off and pm_power_off_prepare callback variables are
removed once all users are converted to the new API. The latter callback
is removed by patch #25 of this series.
4. Ambiguous call chain ordering is prohibited. See patch #5 which adds
verification of restart handlers priorities, ensuring that they are unique.
Changelog:
v3: - Renamed power_handler to sys_off_handler as was suggested by
Rafael Wysocki.
- Improved doc-comments as was suggested by Rafael Wysocki. Added more
doc-comments.
- Implemented full set of 180 patches which convert whole kernel in
accordance to the plan, see link [1] above. Slightly adjusted API to
better suit for the remaining converted drivers.
* Added unregister_sys_off_handler() that is handy for a couple old
platform drivers.
* Dropped devm_register_trivial_restart_handler(), 'simple' variant
is enough to have.
- Improved "Add atomic/blocking_notifier_has_unique_priority()" patch,
as was suggested by Andy Shevchenko. Also replaced down_write() with
down_read() and factored out common notifier_has_unique_priority().
- Added stop_chain field to struct restart_data and reboot_prep_data
after discovering couple drivers wanting that feature.
- Added acks that were given to v2.
v2: - Replaced standalone power-off call chain demo-API with the combined
power-off+restart API because this is what drivers want. It's a more
comprehensive solution.
- Converted multiple drivers and arch code to the new API. Suggested by
Andy Shevchenko. I skimmed through the rest of drivers, verifying that
new API suits them. The rest of the drivers will be converted once we
will settle on the new API, otherwise will be too many patches here.
- v2 API doesn't expose notifier to users and require handlers to
have unique priority. Suggested by Guenter Roeck.
- v2 API has power-off chaining disabled by default and require
drivers to explicitly opt-in to the chaining. This preserves old
behaviour for existing drivers once they are converted to the new
API.
Dmitry Osipenko (25):
notifier: Remove extern annotation from function prototypes
notifier: Add blocking_notifier_call_chain_is_empty()
notifier: Add atomic/blocking_notifier_has_unique_priority()
reboot: Correct typo in a comment
reboot: Warn if restart handler has duplicated priority
reboot: Warn if unregister_restart_handler() fails
reboot: Remove extern annotation from function prototypes
kernel: Add combined power-off+restart handler call chain API
ARM: Use do_kernel_power_off()
csky: Use do_kernel_power_off()
riscv: Use do_kernel_power_off()
arm64: Use do_kernel_power_off()
parisc: Use do_kernel_power_off()
xen/x86: Use do_kernel_power_off()
sh: Use do_kernel_power_off()
x86: Use do_kernel_power_off()
ia64: Use do_kernel_power_off()
mips: Use do_kernel_power_off()
nds32: Use do_kernel_power_off()
powerpc: Use do_kernel_power_off()
m68k: Switch to new sys-off handler API
memory: emif: Use kernel_can_power_off()
ACPI: power: Switch to sys-off handler API
regulator: pfuze100: Use devm_register_sys_off_handler()
reboot: Remove pm_power_off_prepare()
arch/arm/kernel/reboot.c | 4 +-
arch/arm64/kernel/process.c | 3 +-
arch/csky/kernel/power.c | 6 +-
arch/ia64/kernel/process.c | 4 +-
arch/m68k/emu/natfeat.c | 3 +-
arch/m68k/include/asm/machdep.h | 1 -
arch/m68k/kernel/process.c | 5 +-
arch/m68k/kernel/setup_mm.c | 1 -
arch/m68k/kernel/setup_no.c | 1 -
arch/m68k/mac/config.c | 4 +-
arch/mips/kernel/reset.c | 3 +-
arch/nds32/kernel/process.c | 3 +-
arch/parisc/kernel/process.c | 4 +-
arch/powerpc/kernel/setup-common.c | 4 +-
arch/powerpc/xmon/xmon.c | 3 +-
arch/riscv/kernel/reset.c | 12 +-
arch/sh/kernel/reboot.c | 3 +-
arch/x86/kernel/reboot.c | 4 +-
arch/x86/xen/enlighten_pv.c | 4 +-
drivers/acpi/sleep.c | 25 +-
drivers/memory/emif.c | 2 +-
drivers/regulator/pfuze100-regulator.c | 38 +-
include/linux/notifier.h | 37 +-
include/linux/pm.h | 1 -
include/linux/reboot.h | 305 ++++++++++++--
kernel/notifier.c | 83 ++++
kernel/power/hibernate.c | 2 +-
kernel/reboot.c | 556 ++++++++++++++++++++++++-
28 files changed, 985 insertions(+), 136 deletions(-)
--
2.33.1
There is no need to annotate function prototypes with 'extern', it makes
code less readable. Remove unnecessary annotations from <notifier.h>.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
include/linux/notifier.h | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
Emit warning if unregister_restart_handler() fails since it never should
fail. This will ease further API development by catching mistakes early.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
kernel/reboot.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Add sanity check which ensures that there are no two restart handlers
registered with the same priority. Normally it's a direct sign of a
problem if two handlers use the same priority.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
kernel/reboot.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
There is no need to annotate function prototypes with 'extern', it makes
code less readable. Remove unnecessary annotations from <reboot.h>.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
include/linux/reboot.h | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
@@ -40,36 +40,36 @@ extern int reboot_cpu;externintreboot_force;-externintregister_reboot_notifier(structnotifier_block*);-externintunregister_reboot_notifier(structnotifier_block*);+intregister_reboot_notifier(structnotifier_block*);+intunregister_reboot_notifier(structnotifier_block*);-externintdevm_register_reboot_notifier(structdevice*,structnotifier_block*);+intdevm_register_reboot_notifier(structdevice*,structnotifier_block*);-externintregister_restart_handler(structnotifier_block*);-externintunregister_restart_handler(structnotifier_block*);-externvoiddo_kernel_restart(char*cmd);+intregister_restart_handler(structnotifier_block*);+intunregister_restart_handler(structnotifier_block*);+voiddo_kernel_restart(char*cmd);/**Architecture-specificimplementationsofsys_rebootcommands.*/-externvoidmigrate_to_reboot_cpu(void);-externvoidmachine_restart(char*cmd);-externvoidmachine_halt(void);-externvoidmachine_power_off(void);+voidmigrate_to_reboot_cpu(void);+voidmachine_restart(char*cmd);+voidmachine_halt(void);+voidmachine_power_off(void);-externvoidmachine_shutdown(void);+voidmachine_shutdown(void);structpt_regs;-externvoidmachine_crash_shutdown(structpt_regs*);+voidmachine_crash_shutdown(structpt_regs*);/**Architectureindependentimplementationsofsys_rebootcommands.*/-externvoidkernel_restart_prepare(char*cmd);-externvoidkernel_restart(char*cmd);-externvoidkernel_halt(void);-externvoidkernel_power_off(void);+voidkernel_restart_prepare(char*cmd);+voidkernel_restart(char*cmd);+voidkernel_halt(void);+voidkernel_power_off(void);externintC_A_D;/* for sysctl */voidctrl_alt_del(void);
SoC platforms often have multiple ways of how to perform system's
power-off and restart operations. Meanwhile today's kernel is limited to
a single option. Add combined power-off+restart handler call chain API,
which is inspired by the restart API. The new API provides both power-off
and restart functionality.
The old pm_power_off method will be kept around till all users are
converted to the new API.
Current restart API will be replaced by the new unified API since
new API is its superset. The restart functionality of the sys-off handler
API is built upon the existing restart-notifier APIs.
In order to ease conversion to the new API, convenient helpers are added
for the common use-cases. They will reduce amount of boilerplate code and
remove global variables. These helpers preserve old behaviour for cases
where only one power-off handler is expected, this is what all existing
drivers want, and thus, they could be easily converted to the new API.
Users of the new API should explicitly enable power-off chaining by
setting corresponding flag of the power_handler structure.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
include/linux/reboot.h | 265 ++++++++++++++++++-
kernel/power/hibernate.c | 2 +-
kernel/reboot.c | 536 ++++++++++++++++++++++++++++++++++++++-
3 files changed, 795 insertions(+), 8 deletions(-)
@@ -8,10 +8,35 @@structdevice;-#define SYS_DOWN 0x0001 /* Notify of system down */-#define SYS_RESTART SYS_DOWN-#define SYS_HALT 0x0002 /* Notify of system halt */-#define SYS_POWER_OFF 0x0003 /* Notify of system power off */+enumreboot_prepare_mode{+SYS_DOWN=1,/* Notify of system down */+SYS_RESTART=SYS_DOWN,+SYS_HALT,/* Notify of system halt */+SYS_POWER_OFF,/* Notify of system power off */+};++/*+*Standardrestartprioritylevels.Intendedtobesetinthe+*sys_off_handler.restart_priorityfield.+*+*Use`RESTART_PRIO_ABC+-prio`styleforadditionallevels.+*+*RESTART_PRIO_RESERVED:FallsbacktoRESTART_PRIO_DEFAULT.+*Driversmayleavepriorityinitialized+*tozero,toauto-setittothedefaultlevel.+*+*RESTART_PRIO_LOW:Usethisforhandleroflastresort.+*+*RESTART_PRIO_DEFAULT:Usethisfordefault/generichandler.+*+*RESTART_PRIO_HIGH:Usethisifyouhavemultiplehandlersand+*thishandlerhashigherprioritythanthe+*defaulthandler.+*/+#define RESTART_PRIO_RESERVED 0+#define RESTART_PRIO_LOW 8+#define RESTART_PRIO_DEFAULT 128+#define RESTART_PRIO_HIGH 192enumreboot_mode{REBOOT_UNDEFINED=-1,
@@ -294,6 +294,527 @@ void kernel_halt(void)}EXPORT_SYMBOL_GPL(kernel_halt);+/*+*Notifierlistforkernelcodewhichwantstobecalled+*topoweroffthesystem.+*/+staticBLOCKING_NOTIFIER_HEAD(power_off_handler_list);++staticvoiddummy_pm_power_off(void)+{+/* temporary stub until pm_power_off() is gone, see more below */+}++staticstructnotifier_block*pm_power_off_nb;++/**+*register_power_off_handler-Registerfunctiontobecalledtopoweroff+*thesystem+*@nb:Infoabouthandlerfunctiontobecalled+*@nb->priority:Handlerpriority.Handlersshouldfollowthe+*followingguidelinesforsettingpriorities.+*0:Reserved+*1:Power-offhandleroflastresort,+*withlimitedpower-offcapabilities+*128:Defaultpower-offhandler;useifnoother+*power-offhandlerisexpectedtobeavailable,+*and/orifpower-offfunctionalityis+*sufficienttopower-offtheentiresystem+*255:Highestprioritypower-offhandler,will+*preemptallotherpower-offhandlers+*+*Registersafunctionwithcodetobecalledtopoweroffthe+*system.+*+*Registeredfunctionswillbecalledaslaststepofthepower-off+*sequence.+*+*Registeredfunctionsareexpectedtopoweroffthesystemimmediately.+*Ifmorethanonefunctionisregistered,thepower-offhandlerpriority+*selectswhichfunctionwillbecalledfirst.+*+*Power-offhandlersareexpectedtoberegisteredfromnon-architecture+*code,typicallyfromdrivers.Atypicalusecasewouldbeasystem+*wherepower-offfunctionalityisprovidedthroughaPMIC.Multiple+*power-offhandlersmayexist;forexample,onepower-offhandlermight+*turnofftheentiresystem,whileanotheronlyturnsoffpartof+*system.Insuchcases,thepower-offhandlerwhichonlydisablespart+*ofthehardwareisexpectedtoregisterwithlowprioritytoensure+*thatitonlyrunsifnoothermeanstopoweroffthesystemis+*available.+*+*Currentlyalwaysreturnszero,asblocking_notifier_chain_register()+*alwaysreturnszero.+*/+staticintregister_power_off_handler(structnotifier_block*nb)+{+intret;++ret=blocking_notifier_chain_register(&power_off_handler_list,nb);+if(ret)+returnret;++/*+*Handlermusthaveuniquepriority.Otherwisecallorderorderis+*determinedbyregistrationorder,whichisunreliable.+*/+WARN(!blocking_notifier_has_unique_priority(&power_off_handler_list,nb),+"power-off handler must have unique priority\n");++/*+*Somedriverscheckwhetherpm_power_offwasalreadyinstalled.+*InstalldummycallbackusingnewAPItopreserveoldbehaviour+*forthosedriversduringperiodoftransitiontothenewAPI.+*/+if(!pm_power_off){+pm_power_off=dummy_pm_power_off;+pm_power_off_nb=nb;+}++return0;+}++staticintunregister_power_off_handler(structnotifier_block*nb)+{+intret;++if(nb==pm_power_off_nb){+/*+*Checkwhethersomebodyreplacedpm_power_offbehind+*outback.+*/+if(!WARN_ON(pm_power_off!=dummy_pm_power_off))+pm_power_off=NULL;++pm_power_off_nb=NULL;+}++ret=blocking_notifier_chain_unregister(&power_off_handler_list,nb);++returnWARN_ON(ret);+}++staticvoiddevm_unregister_power_off_handler(void*data)+{+structnotifier_block*nb=data;++unregister_power_off_handler(nb);+}++staticintdevm_register_power_off_handler(structdevice*dev,+structnotifier_block*nb)+{+interr;++err=register_power_off_handler(nb);+if(err)+returnerr;++returndevm_add_action_or_reset(dev,devm_unregister_power_off_handler,+nb);+}++staticintsys_off_handler_power_off(structnotifier_block*nb,+unsignedlongmode,void*unused)+{+structpower_off_prep_dataprep_data={};+structsys_off_handler_private_data*priv;+structpower_off_datadata={};+structsys_off_handler*h;+intret=NOTIFY_DONE;++priv=container_of(nb,structsys_off_handler_private_data,power_off_nb);+h=container_of(priv,structsys_off_handler,priv);+prep_data.cb_data=h->cb_data;+data.cb_data=h->cb_data;++switch(mode){+casePOWEROFF_NORMAL:+if(h->power_off_cb)+h->power_off_cb(&data);++if(priv->simple_power_off_cb)+priv->simple_power_off_cb(priv->simple_power_off_cb_data);++if(priv->platform_power_off_cb)+priv->platform_power_off_cb();++if(!h->power_off_chaining_allowed)+ret=NOTIFY_STOP;++break;++casePOWEROFF_PREPARE:+if(h->power_off_prepare_cb)+h->power_off_prepare_cb(&prep_data);++break;++default:+unreachable();+}++returnret;+}++staticintsys_off_handler_restart(structnotifier_block*nb,+unsignedlongmode,void*cmd)+{+structsys_off_handler_private_data*priv;+structrestart_datadata={};+structsys_off_handler*h;++priv=container_of(nb,structsys_off_handler_private_data,restart_nb);+h=container_of(priv,structsys_off_handler,priv);++data.stop_chain=h->restart_chaining_disallowed;+data.cb_data=h->cb_data;+data.mode=mode;+data.cmd=cmd;++h->restart_cb(&data);++returndata.stop_chain?NOTIFY_STOP:NOTIFY_DONE;+}++staticintsys_off_handler_reboot(structnotifier_block*nb,+unsignedlongmode,void*cmd)+{+structsys_off_handler_private_data*priv;+structreboot_prep_datadata={};+structsys_off_handler*h;++priv=container_of(nb,structsys_off_handler_private_data,reboot_nb);+h=container_of(priv,structsys_off_handler,priv);++data.cb_data=h->cb_data;+data.stop_chain=false;+data.mode=mode;+data.cmd=cmd;++h->reboot_prepare_cb(&data);++returndata.stop_chain?NOTIFY_STOP:NOTIFY_DONE;+}++staticstructsys_off_handler_private_data*+sys_off_handler_private_data(structsys_off_handler*handler)+{+return(structsys_off_handler_private_data*)&handler->priv;+}++/**+*devm_register_sys_off_handler-Registersystempower-off/restarthandler+*@dev:Devicethatregistershandler+*@handler:System-offhandler+*+*Registershandlerthatwillbecalledaslaststepofthepower-off+*andrestartsequences.+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intregister_sys_off_handler(structsys_off_handler*handler)+{+structsys_off_handler_private_data*priv;+interr,priority;++priv=sys_off_handler_private_data(handler);++/* sanity-check whether handler is registered twice */+if(WARN_ON(priv->registered))+return-EBUSY;++if(handler->power_off_cb||handler->power_off_prepare_cb){+if(handler->power_off_priority==POWEROFF_PRIO_RESERVED)+priority=POWEROFF_PRIO_DEFAULT;+else+priority=handler->power_off_priority;++priv->power_off_nb.notifier_call=sys_off_handler_power_off;+priv->power_off_nb.priority=priority;++err=register_power_off_handler(&priv->power_off_nb);+if(err)+gotoreset_sys_off_handler;+}++if(handler->restart_cb){+if(handler->restart_priority==RESTART_PRIO_RESERVED)+priority=RESTART_PRIO_DEFAULT;+else+priority=handler->restart_priority;++priv->restart_nb.notifier_call=sys_off_handler_restart;+priv->restart_nb.priority=priority;++err=register_restart_handler(&priv->restart_nb);+if(err)+gotounreg_power_off_handler;+}++if(handler->reboot_prepare_cb){+priv->reboot_nb.notifier_call=sys_off_handler_reboot;+priv->reboot_nb.priority=handler->reboot_priority;++err=register_reboot_notifier(&priv->reboot_nb);+if(err)+gotounreg_restart_handler;+}++priv->registered=true;++return0;++unreg_restart_handler:+if(handler->restart_cb)+unregister_restart_handler(&priv->restart_nb);++unreg_power_off_handler:+if(handler->power_off_cb)+unregister_power_off_handler(&priv->power_off_nb);++reset_sys_off_handler:+memset(priv,0,sizeof(*priv));++returnerr;+}+EXPORT_SYMBOL(register_sys_off_handler);++/**+*unregister_sys_off_handler-Unregistersystempower-off/restarthandler+*@handler:System-offhandler+*+*Unregisterssys-offhandler.Doesnothingandreturnszeroifhandler+*isNULL.+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intunregister_sys_off_handler(structsys_off_handler*handler)+{+structsys_off_handler_private_data*priv;+interr=0;++if(!handler)+return0;++priv=sys_off_handler_private_data(handler);++/* sanity-check whether handler is unregistered twice */+if(WARN_ON(!priv->registered))+return-EINVAL;++if(handler->reboot_prepare_cb)+err|=unregister_reboot_notifier(&priv->reboot_nb);++if(handler->restart_cb)+err|=unregister_restart_handler(&priv->restart_nb);++if(handler->power_off_cb)+err|=unregister_power_off_handler(&priv->power_off_nb);++memset(priv,0,sizeof(*priv));++returnerr?-EINVAL:0;+}+EXPORT_SYMBOL(unregister_sys_off_handler);++staticvoiddevm_unregister_sys_off_handler(void*data)+{+structsys_off_handler*handler=data;++unregister_sys_off_handler(handler);+}++/**+*devm_register_sys_off_handler-Registersystempower-off/restarthandler+*@dev:Devicethatregistershandler+*@handler:System-offhandler+*+*Resource-managedvariantofregister_sys_off_handler().+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intdevm_register_sys_off_handler(structdevice*dev,+structsys_off_handler*handler)+{+interr;++err=register_sys_off_handler(handler);+if(err)+returnerr;++returndevm_add_action_or_reset(dev,devm_unregister_sys_off_handler,+handler);+}+EXPORT_SYMBOL(devm_register_sys_off_handler);++/**+*devm_register_prioritized_power_off_handler-Registerprioritizedpower-offcallback+*@dev:Devicethatregisterscallback+*@priority:Callback'spriority+*@callback:Callbackfunction+*@cb_data:Callback'sargument+*+*Registersresource-managedpower-offcallbackwithagivenpriority.+*Itwillbecalledaslaststepofthepower-offsequence.Further+*lowerprioritycallbackswon'tbeexecutedifthis@callbackfails.+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intdevm_register_prioritized_power_off_handler(structdevice*dev,+intpriority,+void(*callback)(void*data),+void*cb_data)+{+structsys_off_handler_private_data*priv;+structsys_off_handler*handler;++handler=devm_kzalloc(dev,sizeof(*handler),GFP_KERNEL);+if(!handler)+return-ENOMEM;++priv=sys_off_handler_private_data(handler);++priv->power_off_nb.notifier_call=sys_off_handler_power_off;+priv->power_off_nb.priority=priority;+priv->simple_power_off_cb_data=cb_data;+priv->simple_power_off_cb=callback;++returndevm_register_power_off_handler(dev,&priv->power_off_nb);+}+EXPORT_SYMBOL(devm_register_prioritized_power_off_handler);++/**+*devm_register_prioritized_restart_handler-Registerprioritizedrestartcallback+*@dev:Devicethatregisterscallback+*@priority:Callback'spriority+*@callback:Callbackfunction+*@cb_data:Callback'sargument+*+*Registersresource-managedrestartcallbackwithagivenpriority.+*Itwillbecalledasapartoftherestartsequence.Further+*lowerprioritycallbackwillbeexecutedifthis@callbackfails.+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intdevm_register_prioritized_restart_handler(structdevice*dev,+intpriority,+void(*callback)(structrestart_data*data),+void*cb_data)+{+structsys_off_handler*handler;++handler=devm_kzalloc(dev,sizeof(*handler),GFP_KERNEL);+if(!handler)+return-ENOMEM;++handler->restart_priority=priority;+handler->restart_cb=callback;+handler->cb_data=cb_data;++returndevm_register_sys_off_handler(dev,handler);+}+EXPORT_SYMBOL(devm_register_prioritized_restart_handler);++staticstructsys_off_handlerplatform_power_off_handler={+.priv={+.power_off_nb={+.notifier_call=sys_off_handler_power_off,+.priority=POWEROFF_PRIO_PLATFORM,+},+},+};++staticDEFINE_SPINLOCK(platform_power_off_lock);++/**+*register_platform_power_off-Registerplatform-levelpower-offcallback+*@power_off:Power-offcallback+*+*Registerspower-offcallbackthatwillbecalledaslaststep+*ofthepower-offsequence.Thiscallbackisexpectedtobeinvoked+*forthelastresort.Furtherlowerprioritycallbackswon'tbe+*executedif@power_offfails.Onlyoneplatformpower-offcallback+*isallowedtoberegisteredatatime.+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intregister_platform_power_off(void(*power_off)(void))+{+structsys_off_handler_private_data*priv;+interr=0;++priv=sys_off_handler_private_data(&platform_power_off_handler);++spin_lock(&platform_power_off_lock);+if(priv->platform_power_off_cb)+err=-EBUSY;+else+priv->platform_power_off_cb=power_off;+spin_unlock(&platform_power_off_lock);++if(WARN_ON(err))+returnerr;++returnregister_power_off_handler(&priv->power_off_nb);+}+EXPORT_SYMBOL_GPL(register_platform_power_off);++/**+*unregister_platform_power_off-Unregisterplatform-levelpower-offcallback+*@power_off:Power-offcallback+*+*Unregisterspreviouslyregisteredplatformpower-offcallback.+*+*Returnszeroonsuccess,orerrorcodeonfailure.+*/+intunregister_platform_power_off(void(*power_off)(void))+{+structsys_off_handler_private_data*priv;+intret;++priv=sys_off_handler_private_data(&platform_power_off_handler);++if(WARN_ON(priv->platform_power_off_cb!=power_off))+return-EINVAL;++ret=unregister_power_off_handler(&priv->power_off_nb);+priv->platform_power_off_cb=NULL;++returnret;+}+EXPORT_SYMBOL_GPL(unregister_platform_power_off);++/**+*do_kernel_power_off-Executekernelpower-offhandlercallchain+*+*Callsfunctionsregisteredwithregister_power_off_handler.+*+*Expectedtobecalledaslaststepofthepower-offsequence.+*+*Powersoffthesystemimmediatelyifapower-offhandlerfunctionhas+*beenregistered.Otherwisedoesnothing.+*/+voiddo_kernel_power_off(void)+{+/* legacy pm_power_off() is unchained and has highest priority */+if(pm_power_off&&pm_power_off!=dummy_pm_power_off)+returnpm_power_off();++blocking_notifier_call_chain(&power_off_handler_list,POWEROFF_NORMAL,+NULL);+}++staticvoiddo_kernel_power_off_prepare(void)+{+/* legacy pm_power_off_prepare() is unchained and has highest priority */+if(pm_power_off_prepare)+returnpm_power_off_prepare();++blocking_notifier_call_chain(&power_off_handler_list,POWEROFF_PREPARE,+NULL);+}+/***kernel_power_off-power_offthesystem*
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Reviewed-by: Russell King (Oracle) <redacted>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/arm/kernel/reboot.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Acked-by: Guo Ren <guoren@kernel.org>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/csky/kernel/power.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Acked-by: Palmer Dabbelt <palmer@dabbelt.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/riscv/kernel/reset.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/arm64/kernel/process.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Acked-by: Helge Deller <deller@gmx.de> # parisc
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/parisc/kernel/process.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -114,8 +115,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.Theuserhastopressthebuttonhimself.*/
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Acked-by: Juergen Gross <jgross@suse.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/x86/xen/enlighten_pv.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/sh/kernel/reboot.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/x86/kernel/reboot.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -747,10 +747,10 @@ static void native_machine_halt(void)staticvoidnative_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);
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/ia64/kernel/process.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/mips/kernel/reset.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/nds32/kernel/process.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/powerpc/kernel/setup-common.c | 4 +---
arch/powerpc/xmon/xmon.c | 3 +--
2 files changed, 2 insertions(+), 5 deletions(-)
Kernel now supports chained power-off handlers. Use
register_power_off_handler() that registers power-off handlers and
do_kernel_power_off() that invokes chained power-off handlers. Legacy
pm_power_off() will be removed once all drivers will be converted to
the new power-off API.
Normally arch code should adopt only the do_kernel_power_off() at first,
but m68k is a special case because it uses pm_power_off() "inside out",
i.e. pm_power_off() invokes machine_power_off() [in fact it does nothing],
while it's machine_power_off() that should invoke the pm_power_off(), and
thus, we can't convert platforms to the new API separately. There are only
two platforms changed here, so it's not a big deal.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/m68k/emu/natfeat.c | 3 ++-
arch/m68k/include/asm/machdep.h | 1 -
arch/m68k/kernel/process.c | 5 ++---
arch/m68k/kernel/setup_mm.c | 1 -
arch/m68k/kernel/setup_no.c | 1 -
arch/m68k/mac/config.c | 4 +++-
6 files changed, 7 insertions(+), 8 deletions(-)
@@ -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");
@@ -1020,7 +1012,7 @@ static void acpi_sleep_hibernate_setup(void)staticinlinevoidacpi_sleep_hibernate_setup(void){}#endif /* !CONFIG_HIBERNATION */-staticvoidacpi_power_off_prepare(void)+staticvoidacpi_power_off_prepare(structpower_off_prep_data*data){/* Prepare to power off the system */acpi_sleep_prepare(ACPI_STATE_S5);
@@ -1028,7 +1020,7 @@ static void acpi_power_off_prepare(void)acpi_os_wait_events_complete();}-staticvoidacpi_power_off(void)+staticvoidacpi_power_off(structpower_off_data*data){/* acpi_sleep_prepare(ACPI_STATE_S5) should have already been called */pr_debug("%s called\n",__func__);
Use devm_register_sys_off_handler() that replaces global
pm_power_off_prepare variable and allows to register multiple
power-off handlers.
Acked-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/regulator/pfuze100-regulator.c | 38 ++++++++++----------------
1 file changed, 14 insertions(+), 24 deletions(-)
@@ -569,10 +571,10 @@ static inline struct device_node *match_of_node(int index)returnpfuze_matches[index].of_node;}-staticstructpfuze_chip*syspm_pfuze_chip;--staticvoidpfuze_power_off_prepare(void)+staticvoidpfuze_power_off_prepare(structpower_off_prep_data*data){+structpfuze_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 */
@@ -611,24 +613,23 @@ static void pfuze_power_off_prepare(void)staticintpfuze_power_off_prepare_init(structpfuze_chip*pfuze_chip){+interr;+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;-}+pfuze_chip->sys_off.power_off_prepare_cb=pfuze_power_off_prepare;+pfuze_chip->sys_off.cb_data=pfuze_chip;-if(syspm_pfuze_chip){-dev_warn(pfuze_chip->dev,"syspm_pfuze_chip is already set.\n");-return-EBUSY;+err=devm_register_sys_off_handler(pfuze_chip->dev,&pfuze_chip->sys_off);+if(err){+dev_err(pfuze_chip->dev,+"failed to register sys-off handler: %d\n",err);+returnerr;}-syspm_pfuze_chip=pfuze_chip;-pm_power_off_prepare=pfuze_power_off_prepare;-return0;}
@@ -837,23 +838,12 @@ static int pfuze100_regulator_probe(struct i2c_client *client,return0;}-staticintpfuze100_regulator_remove(structi2c_client*client)-{-if(syspm_pfuze_chip){-syspm_pfuze_chip=NULL;-pm_power_off_prepare=NULL;-}--return0;-}-staticstructi2c_driverpfuze_driver={.driver={.name="pfuze100-regulator",.of_match_table=pfuze_dt_ids,},.probe=pfuze100_regulator_probe,-.remove=pfuze100_regulator_remove,};module_i2c_driver(pfuze_driver);
@@ -20,7 +20,6 @@*Callbacksforplatformdriverstoimplement.*/externvoid(*pm_power_off)(void);-externvoid(*pm_power_off_prepare)(void);structdevice;/* we have a circular dep with device.h */#ifdef CONFIG_VT_CONSOLE_SLEEP
@@ -48,13 +48,6 @@ int reboot_cpu;enumreboot_typereboot_type=BOOT_ACPI;intreboot_force;-/*-*Ifset,thisisusedforpreparingthesystemtopoweroff.-*/--void(*pm_power_off_prepare)(void);-EXPORT_SYMBOL_GPL(pm_power_off_prepare);-/***emergency_restart-rebootthesystem*
@@ -807,10 +800,6 @@ void do_kernel_power_off(void)staticvoiddo_kernel_power_off_prepare(void){-/* legacy pm_power_off_prepare() is unchained and has highest priority */-if(pm_power_off_prepare)-returnpm_power_off_prepare();-blocking_notifier_call_chain(&power_off_handler_list,POWEROFF_PREPARE,NULL);}
Problem
-------
SoC devices require power-off call chaining functionality from kernel.
We have a widely used restart chaining provided by restart notifier API,
but nothing for power-off.
Solution
--------
Introduce new API that provides both restart and power-off call chains.
Why combine restart with power-off? Because drivers often do both.
More practical to have API that provides both under the same roof.
The new API is designed with simplicity and extensibility in mind.
It's built upon the existing restart and reboot APIs. The simplicity
is in new helper functions that are convenient for drivers. The
extensibility is in the design that doesn't hardcode callback
arguments, making easy to add new parameters and remove old.
This is a third attempt to introduce the new API. First was made by
Guenter Roeck back in 2014, second was made by Thierry Reding in 2017.
In fact the work didn't stop and recently arm_pm_restart() was removed
from v5.14 kernel, which was a part of preparatory work started by
Guenter Roeck. I took into account experience and ideas from the
previous attempts, extended and polished them.
Adoption plan
-------------
This patchset introduces the new API. It also converts multiple drivers
and arch code to the new API to demonstrate how it all looks in practice.
The plan is:
1. Merge new API (patches 1-8). This API will co-exist with the old APIs.
2. Convert arch code to do_kernel_power_off() (patches 9-21).
3. Convert drivers and platform code to the new API.
4. Remove obsolete pm_power_off and pm_power_off_prepare variables.
5. Make restart-notifier API private to kernel/reboot.c once no users left.
It's fully implemented here:
[1] https://github.com/grate-driver/linux/commits/sys-off-handler
For now I'm sending only the first 25 base patches out of ~180. It's
preferable to squash 1-2, partially 3 and 4 points of the plan into a
single patchset to ease and speed up applying of the rest of the patches.
Majority of drivers and platform patches depend on the base, hence they
will come later (and per subsystem), once base will land.
All [1] patches are compile-tested. Tegra and x86 ACPI patches are tested
on hardware. The remaining should be covered by unit tests (unpublished).
Results
-------
1. Devices can be powered off properly.
2. Global variables are removed from drivers.
3. Global pm_power_off and pm_power_off_prepare callback variables are
removed once all users are converted to the new API. The latter callback
is removed by patch #25 of this series.
4. Ambiguous call chain ordering is prohibited. See patch #5 which adds
verification of restart handlers priorities, ensuring that they are unique.
Changelog:
v3: - Renamed power_handler to sys_off_handler as was suggested by
Rafael Wysocki.
- Improved doc-comments as was suggested by Rafael Wysocki. Added more
doc-comments.
- Implemented full set of 180 patches which convert whole kernel in
accordance to the plan, see link [1] above. Slightly adjusted API to
better suit for the remaining converted drivers.
* Added unregister_sys_off_handler() that is handy for a couple old
platform drivers.
* Dropped devm_register_trivial_restart_handler(), 'simple' variant
is enough to have.
- Improved "Add atomic/blocking_notifier_has_unique_priority()" patch,
as was suggested by Andy Shevchenko. Also replaced down_write() with
down_read() and factored out common notifier_has_unique_priority().
- Added stop_chain field to struct restart_data and reboot_prep_data
after discovering couple drivers wanting that feature.
- Added acks that were given to v2.
v2: - Replaced standalone power-off call chain demo-API with the combined
power-off+restart API because this is what drivers want. It's a more
comprehensive solution.
- Converted multiple drivers and arch code to the new API. Suggested by
Andy Shevchenko. I skimmed through the rest of drivers, verifying that
new API suits them. The rest of the drivers will be converted once we
will settle on the new API, otherwise will be too many patches here.
- v2 API doesn't expose notifier to users and require handlers to
have unique priority. Suggested by Guenter Roeck.
- v2 API has power-off chaining disabled by default and require
drivers to explicitly opt-in to the chaining. This preserves old
behaviour for existing drivers once they are converted to the new
API.
Dmitry Osipenko (25):
notifier: Remove extern annotation from function prototypes
notifier: Add blocking_notifier_call_chain_is_empty()
notifier: Add atomic/blocking_notifier_has_unique_priority()
reboot: Correct typo in a comment
reboot: Warn if restart handler has duplicated priority
reboot: Warn if unregister_restart_handler() fails
reboot: Remove extern annotation from function prototypes
kernel: Add combined power-off+restart handler call chain API
ARM: Use do_kernel_power_off()
csky: Use do_kernel_power_off()
riscv: Use do_kernel_power_off()
arm64: Use do_kernel_power_off()
parisc: Use do_kernel_power_off()
xen/x86: Use do_kernel_power_off()
sh: Use do_kernel_power_off()
x86: Use do_kernel_power_off()
ia64: Use do_kernel_power_off()
mips: Use do_kernel_power_off()
nds32: Use do_kernel_power_off()
powerpc: Use do_kernel_power_off()
m68k: Switch to new sys-off handler API
memory: emif: Use kernel_can_power_off()
ACPI: power: Switch to sys-off handler API
regulator: pfuze100: Use devm_register_sys_off_handler()
reboot: Remove pm_power_off_prepare()
arch/arm/kernel/reboot.c | 4 +-
arch/arm64/kernel/process.c | 3 +-
arch/csky/kernel/power.c | 6 +-
arch/ia64/kernel/process.c | 4 +-
arch/m68k/emu/natfeat.c | 3 +-
arch/m68k/include/asm/machdep.h | 1 -
arch/m68k/kernel/process.c | 5 +-
arch/m68k/kernel/setup_mm.c | 1 -
arch/m68k/kernel/setup_no.c | 1 -
arch/m68k/mac/config.c | 4 +-
arch/mips/kernel/reset.c | 3 +-
arch/nds32/kernel/process.c | 3 +-
arch/parisc/kernel/process.c | 4 +-
arch/powerpc/kernel/setup-common.c | 4 +-
arch/powerpc/xmon/xmon.c | 3 +-
arch/riscv/kernel/reset.c | 12 +-
arch/sh/kernel/reboot.c | 3 +-
arch/x86/kernel/reboot.c | 4 +-
arch/x86/xen/enlighten_pv.c | 4 +-
drivers/acpi/sleep.c | 25 +-
drivers/memory/emif.c | 2 +-
drivers/regulator/pfuze100-regulator.c | 38 +-
include/linux/notifier.h | 37 +-
include/linux/pm.h | 1 -
include/linux/reboot.h | 305 ++++++++++++--
kernel/notifier.c | 83 ++++
kernel/power/hibernate.c | 2 +-
kernel/reboot.c | 556 ++++++++++++++++++++++++-
28 files changed, 985 insertions(+), 136 deletions(-)
On Mon, Nov 8, 2021 at 1:48 AM Dmitry Osipenko [off-list ref] wrote:
Kernel now supports chained power-off handlers. Use
register_power_off_handler() that registers power-off handlers and
do_kernel_power_off() that invokes chained power-off handlers. Legacy
pm_power_off() will be removed once all drivers will be converted to
the new power-off API.
Normally arch code should adopt only the do_kernel_power_off() at first,
but m68k is a special case because it uses pm_power_off() "inside out",
i.e. pm_power_off() invokes machine_power_off() [in fact it does nothing],
while it's machine_power_off() that should invoke the pm_power_off(), and
thus, we can't convert platforms to the new API separately. There are only
two platforms changed here, so it's not a big deal.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Acked-by: Geert Uytterhoeven <geert@linux-m68k.org>
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-11-08 12:01:59
Dmitry Osipenko [off-list ref] writes:
Kernel now supports chained power-off handlers. Use do_kernel_power_off()
that invokes chained power-off handlers. It also invokes legacy
pm_power_off() for now, which will be removed once all drivers will
be converted to the new power-off API.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
arch/powerpc/kernel/setup-common.c | 4 +---
arch/powerpc/xmon/xmon.c | 3 +--
2 files changed, 2 insertions(+), 5 deletions(-)
Acked-by: Michael Ellerman <mpe@ellerman.id.au>
cheers