Files
android_kernel_xiaomi_sm8450/drivers/base/power/main.c
Greg Kroah-Hartman 66e91da883 Merge 5.10.210 into android12-5.10-lts
Changes in 5.10.210
	usb: cdns3: Fixes for sparse warnings
	usb: cdns3: fix uvc failure work since sg support enabled
	usb: cdns3: fix incorrect calculation of ep_buf_size when more than one config
	usb: cdns3: fix iso transfer error when mult is not zero
	usb: cdns3: Fix uvc fail when DMA cross 4k boundery since sg enabled
	PCI: mediatek: Clear interrupt status before dispatching handler
	units: change from 'L' to 'UL'
	units: add the HZ macros
	serial: sc16is7xx: set safe default SPI clock frequency
	spi: introduce SPI_MODE_X_MASK macro
	serial: sc16is7xx: add check for unsupported SPI modes during probe
	iio: adc: ad7091r: Set alert bit in config register
	iio: adc: ad7091r: Allow users to configure device events
	iio: adc: ad7091r: Enable internal vref if external vref is not supplied
	dmaengine: fix NULL pointer in channel unregistration function
	iio:adc:ad7091r: Move exports into IIO_AD7091R namespace.
	ext4: allow for the last group to be marked as trimmed
	crypto: api - Disallow identical driver names
	PM: hibernate: Enforce ordering during image compression/decompression
	hwrng: core - Fix page fault dead lock on mmap-ed hwrng
	crypto: s390/aes - Fix buffer overread in CTR mode
	rpmsg: virtio: Free driver_override when rpmsg_remove()
	bus: mhi: host: Drop chan lock before queuing buffers
	parisc/firmware: Fix F-extend for PDC addresses
	async: Split async_schedule_node_domain()
	async: Introduce async_schedule_dev_nocall()
	arm64: dts: qcom: sdm845: fix USB wakeup interrupt types
	arm64: dts: qcom: sdm845: fix USB DP/DM HS PHY interrupts
	lsm: new security_file_ioctl_compat() hook
	scripts/get_abi: fix source path leak
	mmc: core: Use mrq.sbc in close-ended ffu
	mmc: mmc_spi: remove custom DMA mapped buffers
	rtc: Adjust failure return code for cmos_set_alarm()
	nouveau/vmm: don't set addr on the fail path to avoid warning
	ubifs: ubifs_symlink: Fix memleak of inode->i_link in error path
	rename(): fix the locking of subdirectories
	block: Remove special-casing of compound pages
	stddef: Introduce DECLARE_FLEX_ARRAY() helper
	smb3: Replace smb2pdu 1-element arrays with flex-arrays
	mm: vmalloc: introduce array allocation functions
	KVM: use __vcalloc for very large allocations
	net/smc: fix illegal rmb_desc access in SMC-D connection dump
	tcp: make sure init the accept_queue's spinlocks once
	bnxt_en: Wait for FLR to complete during probe
	vlan: skip nested type that is not IFLA_VLAN_QOS_MAPPING
	llc: make llc_ui_sendmsg() more robust against bonding changes
	llc: Drop support for ETH_P_TR_802_2.
	net/rds: Fix UBSAN: array-index-out-of-bounds in rds_cmsg_recv
	tracing: Ensure visibility when inserting an element into tracing_map
	afs: Hide silly-rename files from userspace
	tcp: Add memory barrier to tcp_push()
	netlink: fix potential sleeping issue in mqueue_flush_file
	ipv6: init the accept_queue's spinlocks in inet6_create
	net/mlx5: DR, Use the right GVMI number for drop action
	net/mlx5e: fix a double-free in arfs_create_groups
	netfilter: nf_tables: restrict anonymous set and map names to 16 bytes
	netfilter: nf_tables: validate NFPROTO_* family
	net: mvpp2: clear BM pool before initialization
	selftests: netdevsim: fix the udp_tunnel_nic test
	fjes: fix memleaks in fjes_hw_setup
	net: fec: fix the unhandled context fault from smmu
	btrfs: ref-verify: free ref cache before clearing mount opt
	btrfs: tree-checker: fix inline ref size in error messages
	btrfs: don't warn if discard range is not aligned to sector
	btrfs: defrag: reject unknown flags of btrfs_ioctl_defrag_range_args
	btrfs: don't abort filesystem when attempting to snapshot deleted subvolume
	rbd: don't move requests to the running list on errors
	exec: Fix error handling in begin_new_exec()
	wifi: iwlwifi: fix a memory corruption
	netfilter: nft_chain_filter: handle NETDEV_UNREGISTER for inet/ingress basechain
	netfilter: nf_tables: reject QUEUE/DROP verdict parameters
	gpiolib: acpi: Ignore touchpad wakeup on GPD G1619-04
	drm: Don't unref the same fb many times by mistake due to deadlock handling
	drm/bridge: nxp-ptn3460: fix i2c_master_send() error checking
	drm/tidss: Fix atomic_flush check
	drm/bridge: nxp-ptn3460: simplify some error checking
	PM: sleep: Use dev_printk() when possible
	PM: sleep: Avoid calling put_device() under dpm_list_mtx
	PM: core: Remove unnecessary (void *) conversions
	PM: sleep: Fix possible deadlocks in core system-wide PM code
	fs/pipe: move check to pipe_has_watch_queue()
	pipe: wakeup wr_wait after setting max_usage
	ARM: dts: samsung: exynos4210-i9100: Unconditionally enable LDO12
	arm64: dts: qcom: sc7180: Use pdc interrupts for USB instead of GIC interrupts
	arm64: dts: qcom: sc7180: fix USB wakeup interrupt types
	media: mtk-jpeg: Fix use after free bug due to error path handling in mtk_jpeg_dec_device_run
	mm: use __pfn_to_section() instead of open coding it
	mm/sparsemem: fix race in accessing memory_section->usage
	btrfs: remove err variable from btrfs_delete_subvolume
	btrfs: avoid copying BTRFS_ROOT_SUBVOL_DEAD flag to snapshot of subvolume being deleted
	drm: panel-simple: add missing bus flags for Tianma tm070jvhg[30/33]
	drm/exynos: fix accidental on-stack copy of exynos_drm_plane
	drm/exynos: gsc: minor fix for loop iteration in gsc_runtime_resume
	gpio: eic-sprd: Clear interrupt after set the interrupt type
	spi: bcm-qspi: fix SFDP BFPT read by usig mspi read
	mips: Call lose_fpu(0) before initializing fcr31 in mips_set_personality_nan
	tick/sched: Preserve number of idle sleeps across CPU hotplug events
	x86/entry/ia32: Ensure s32 is sign extended to s64
	powerpc/mm: Fix null-pointer dereference in pgtable_cache_add
	drivers/perf: pmuv3: don't expose SW_INCR event in sysfs
	powerpc: Fix build error due to is_valid_bugaddr()
	powerpc/mm: Fix build failures due to arch_reserved_kernel_pages()
	x86/boot: Ignore NMIs during very early boot
	powerpc: pmd_move_must_withdraw() is only needed for CONFIG_TRANSPARENT_HUGEPAGE
	powerpc/lib: Validate size for vector operations
	x86/mce: Mark fatal MCE's page as poison to avoid panic in the kdump kernel
	perf/core: Fix narrow startup race when creating the perf nr_addr_filters sysfs file
	debugobjects: Stop accessing objects after releasing hash bucket lock
	regulator: core: Only increment use_count when enable_count changes
	audit: Send netlink ACK before setting connection in auditd_set
	ACPI: video: Add quirk for the Colorful X15 AT 23 Laptop
	PNP: ACPI: fix fortify warning
	ACPI: extlog: fix NULL pointer dereference check
	PM / devfreq: Synchronize devfreq_monitor_[start/stop]
	ACPI: APEI: set memory failure flags as MF_ACTION_REQUIRED on synchronous events
	FS:JFS:UBSAN:array-index-out-of-bounds in dbAdjTree
	UBSAN: array-index-out-of-bounds in dtSplitRoot
	jfs: fix slab-out-of-bounds Read in dtSearch
	jfs: fix array-index-out-of-bounds in dbAdjTree
	jfs: fix uaf in jfs_evict_inode
	pstore/ram: Fix crash when setting number of cpus to an odd number
	crypto: stm32/crc32 - fix parsing list of devices
	afs: fix the usage of read_seqbegin_or_lock() in afs_lookup_volume_rcu()
	afs: fix the usage of read_seqbegin_or_lock() in afs_find_server*()
	rxrpc_find_service_conn_rcu: fix the usage of read_seqbegin_or_lock()
	jfs: fix array-index-out-of-bounds in diNewExt
	s390/ptrace: handle setting of fpc register correctly
	KVM: s390: fix setting of fpc register
	SUNRPC: Fix a suspicious RCU usage warning
	ecryptfs: Reject casefold directory inodes
	ext4: fix inconsistent between segment fstrim and full fstrim
	ext4: unify the type of flexbg_size to unsigned int
	ext4: remove unnecessary check from alloc_flex_gd()
	ext4: avoid online resizing failures due to oversized flex bg
	wifi: rt2x00: restart beacon queue when hardware reset
	selftests/bpf: satisfy compiler by having explicit return in btf test
	selftests/bpf: Fix pyperf180 compilation failure with clang18
	scsi: lpfc: Fix possible file string name overflow when updating firmware
	PCI: Add no PM reset quirk for NVIDIA Spectrum devices
	bonding: return -ENOMEM instead of BUG in alb_upper_dev_walk
	scsi: arcmsr: Support new PCI device IDs 1883 and 1886
	ARM: dts: imx7d: Fix coresight funnel ports
	ARM: dts: imx7s: Fix lcdif compatible
	ARM: dts: imx7s: Fix nand-controller #size-cells
	wifi: ath9k: Fix potential array-index-out-of-bounds read in ath9k_htc_txstatus()
	bpf: Add map and need_defer parameters to .map_fd_put_ptr()
	scsi: libfc: Don't schedule abort twice
	scsi: libfc: Fix up timeout error in fc_fcp_rec_error()
	bpf: Set uattr->batch.count as zero before batched update or deletion
	ARM: dts: rockchip: fix rk3036 hdmi ports node
	ARM: dts: imx25/27-eukrea: Fix RTC node name
	ARM: dts: imx: Use flash@0,0 pattern
	ARM: dts: imx27: Fix sram node
	ARM: dts: imx1: Fix sram node
	ionic: pass opcode to devcmd_wait
	block/rnbd-srv: Check for unlikely string overflow
	ARM: dts: imx25: Fix the iim compatible string
	ARM: dts: imx25/27: Pass timing0
	ARM: dts: imx27-apf27dev: Fix LED name
	ARM: dts: imx23-sansa: Use preferred i2c-gpios properties
	ARM: dts: imx23/28: Fix the DMA controller node name
	net: dsa: mv88e6xxx: Fix mv88e6352_serdes_get_stats error path
	block: prevent an integer overflow in bvec_try_merge_hw_page
	md: Whenassemble the array, consult the superblock of the freshest device
	arm64: dts: qcom: msm8996: Fix 'in-ports' is a required property
	arm64: dts: qcom: msm8998: Fix 'out-ports' is a required property
	wifi: rtl8xxxu: Add additional USB IDs for RTL8192EU devices
	wifi: rtlwifi: rtl8723{be,ae}: using calculate_bit_shift()
	wifi: cfg80211: free beacon_ies when overridden from hidden BSS
	Bluetooth: qca: Set both WIDEBAND_SPEECH and LE_STATES quirks for QCA2066
	Bluetooth: L2CAP: Fix possible multiple reject send
	i40e: Fix VF disable behavior to block all traffic
	f2fs: fix to check return value of f2fs_reserve_new_block()
	ALSA: hda: Refer to correct stream index at loops
	ASoC: doc: Fix undefined SND_SOC_DAPM_NOPM argument
	fast_dput(): handle underflows gracefully
	RDMA/IPoIB: Fix error code return in ipoib_mcast_join
	drm/amd/display: Fix tiled display misalignment
	f2fs: fix write pointers on zoned device after roll forward
	drm/drm_file: fix use of uninitialized variable
	drm/framebuffer: Fix use of uninitialized variable
	drm/mipi-dsi: Fix detach call without attach
	media: stk1160: Fixed high volume of stk1160_dbg messages
	media: rockchip: rga: fix swizzling for RGB formats
	PCI: add INTEL_HDA_ARL to pci_ids.h
	ALSA: hda: Intel: add HDA_ARL PCI ID support
	ALSA: hda: intel-dspcfg: add filters for ARL-S and ARL
	drm/exynos: Call drm_atomic_helper_shutdown() at shutdown/unbind time
	IB/ipoib: Fix mcast list locking
	media: ddbridge: fix an error code problem in ddb_probe
	drm/msm/dpu: Ratelimit framedone timeout msgs
	clk: hi3620: Fix memory leak in hi3620_mmc_clk_init()
	clk: mmp: pxa168: Fix memory leak in pxa168_clk_init()
	watchdog: it87_wdt: Keep WDTCTRL bit 3 unmodified for IT8784/IT8786
	drm/amdgpu: Let KFD sync with VM fences
	drm/amdgpu: Drop 'fence' check in 'to_amdgpu_amdkfd_fence()'
	leds: trigger: panic: Don't register panic notifier if creating the trigger failed
	um: Fix naming clash between UML and scheduler
	um: Don't use vfprintf() for os_info()
	um: net: Fix return type of uml_net_start_xmit()
	i3c: master: cdns: Update maximum prescaler value for i2c clock
	xen/gntdev: Fix the abuse of underlying struct page in DMA-buf import
	mfd: ti_am335x_tscadc: Fix TI SoC dependencies
	PCI: Only override AMD USB controller if required
	PCI: switchtec: Fix stdev_release() crash after surprise hot remove
	usb: hub: Replace hardcoded quirk value with BIT() macro
	tty: allow TIOCSLCKTRMIOS with CAP_CHECKPOINT_RESTORE
	fs/kernfs/dir: obey S_ISGID
	PCI/AER: Decode Requester ID when no error info found
	libsubcmd: Fix memory leak in uniq()
	virtio_net: Fix "‘%d’ directive writing between 1 and 11 bytes into a region of size 10" warnings
	blk-mq: fix IO hang from sbitmap wakeup race
	ceph: fix deadlock or deadcode of misusing dget()
	drm/amd/powerplay: Fix kzalloc parameter 'ATOM_Tonga_PPM_Table' in 'get_platform_power_management_table()'
	drm/amdgpu: Release 'adev->pm.fw' before return in 'amdgpu_device_need_post()'
	perf: Fix the nr_addr_filters fix
	wifi: cfg80211: fix RCU dereference in __cfg80211_bss_update
	drm: using mul_u32_u32() requires linux/math64.h
	scsi: isci: Fix an error code problem in isci_io_request_build()
	scsi: core: Introduce enum scsi_disposition
	scsi: core: Move scsi_host_busy() out of host lock for waking up EH handler
	ip6_tunnel: use dev_sw_netstats_rx_add()
	ip6_tunnel: make sure to pull inner header in __ip6_tnl_rcv()
	net-zerocopy: Refactor frag-is-remappable test.
	tcp: add sanity checks to rx zerocopy
	ixgbe: Remove non-inclusive language
	ixgbe: Refactor returning internal error codes
	ixgbe: Refactor overtemp event handling
	ixgbe: Fix an error handling path in ixgbe_read_iosf_sb_reg_x550()
	ipv6: Ensure natural alignment of const ipv6 loopback and router addresses
	llc: call sock_orphan() at release time
	netfilter: nf_log: replace BUG_ON by WARN_ON_ONCE when putting logger
	netfilter: nft_ct: sanitize layer 3 and 4 protocol number in custom expectations
	net: ipv4: fix a memleak in ip_setup_cork
	af_unix: fix lockdep positive in sk_diag_dump_icons()
	net: sysfs: Fix /sys/class/net/<iface> path
	HID: apple: Add support for the 2021 Magic Keyboard
	HID: apple: Add 2021 magic keyboard FN key mapping
	bonding: remove print in bond_verify_device_path
	uapi: stddef.h: Fix __DECLARE_FLEX_ARRAY for C++
	PM: sleep: Fix error handling in dpm_prepare()
	dmaengine: fsl-dpaa2-qdma: Fix the size of dma pools
	dmaengine: ti: k3-udma: Report short packet errors
	dmaengine: fsl-qdma: Fix a memory leak related to the status queue DMA
	dmaengine: fsl-qdma: Fix a memory leak related to the queue command DMA
	phy: renesas: rcar-gen3-usb2: Fix returning wrong error code
	dmaengine: fix is_slave_direction() return false when DMA_DEV_TO_DEV
	phy: ti: phy-omap-usb2: Fix NULL pointer dereference for SRP
	drm/msm/dp: return correct Colorimetry for DP_TEST_DYNAMIC_RANGE_CEA case
	net: stmmac: xgmac: fix handling of DPP safety error for DMA channels
	selftests: net: avoid just another constant wait
	tunnels: fix out of bounds access when building IPv6 PMTU error
	atm: idt77252: fix a memleak in open_card_ubr0
	hwmon: (aspeed-pwm-tacho) mutex for tach reading
	hwmon: (coretemp) Fix out-of-bounds memory access
	hwmon: (coretemp) Fix bogus core_id to attr name mapping
	inet: read sk->sk_family once in inet_recv_error()
	rxrpc: Fix response to PING RESPONSE ACKs to a dead call
	tipc: Check the bearer type before calling tipc_udp_nl_bearer_add()
	ppp_async: limit MRU to 64K
	netfilter: nft_compat: reject unused compat flag
	netfilter: nft_compat: restrict match/target protocol to u16
	netfilter: nft_ct: reject direction for ct id
	netfilter: nft_set_pipapo: store index in scratch maps
	netfilter: nft_set_pipapo: add helper to release pcpu scratch area
	netfilter: nft_set_pipapo: remove scratch_aligned pointer
	scsi: core: Move scsi_host_busy() out of host lock if it is for per-command
	blk-iocost: Fix an UBSAN shift-out-of-bounds warning
	net/af_iucv: clean up a try_then_request_module()
	USB: serial: qcserial: add new usb-id for Dell Wireless DW5826e
	USB: serial: option: add Fibocom FM101-GL variant
	USB: serial: cp210x: add ID for IMST iM871A-USB
	usb: host: xhci-plat: Add support for XHCI_SG_TRB_CACHE_SIZE_QUIRK
	hrtimer: Report offline hrtimer enqueue
	Input: i8042 - fix strange behavior of touchpad on Clevo NS70PU
	Input: atkbd - skip ATKBD_CMD_SETLEDS when skipping ATKBD_CMD_GETID
	vhost: use kzalloc() instead of kmalloc() followed by memset()
	clocksource: Skip watchdog check for large watchdog intervals
	net: stmmac: xgmac: use #define for string constants
	net: stmmac: xgmac: fix a typo of register name in DPP safety handling
	netfilter: nft_set_rbtree: skip end interval element from gc
	btrfs: forbid creating subvol qgroups
	btrfs: do not ASSERT() if the newly created subvolume already got read
	btrfs: forbid deleting live subvol qgroup
	btrfs: send: return EOPNOTSUPP on unknown flags
	of: unittest: Fix compile in the non-dynamic case
	net: openvswitch: limit the number of recursions from action sets
	spi: ppc4xx: Drop write-only variable
	ASoC: rt5645: Fix deadlock in rt5645_jack_detect_work()
	net: sysfs: Fix /sys/class/net/<iface> path for statistics
	MIPS: Add 'memory' clobber to csum_ipv6_magic() inline assembler
	i40e: Fix waiting for queues of all VSIs to be disabled
	tracing/trigger: Fix to return error if failed to alloc snapshot
	mm/writeback: fix possible divide-by-zero in wb_dirty_limits(), again
	ALSA: hda/realtek: Fix the external mic not being recognised for Acer Swift 1 SF114-32
	ALSA: hda/realtek: Enable Mute LED on HP Laptop 14-fq0xxx
	HID: wacom: generic: Avoid reporting a serial of '0' to userspace
	HID: wacom: Do not register input devices until after hid_hw_start
	usb: ucsi_acpi: Fix command completion handling
	USB: hub: check for alternate port before enabling A_ALT_HNP_SUPPORT
	usb: f_mass_storage: forbid async queue when shutdown happen
	media: ir_toy: fix a memleak in irtoy_tx
	powerpc/kasan: Fix addr error caused by page alignment
	i2c: i801: Remove i801_set_block_buffer_mode
	i2c: i801: Fix block process call transactions
	modpost: trim leading spaces when processing source files list
	scsi: Revert "scsi: fcoe: Fix potential deadlock on &fip->ctlr_lock"
	lsm: fix the logic in security_inode_getsecctx()
	firewire: core: correct documentation of fw_csr_string() kernel API
	kbuild: Fix changing ELF file type for output of gen_btf for big endian
	nfc: nci: free rx_data_reassembly skb on NCI device cleanup
	net: hsr: remove WARN_ONCE() in send_hsr_supervision_frame()
	xen-netback: properly sync TX responses
	ALSA: hda/realtek: Enable headset mic on Vaio VJFE-ADL
	binder: signal epoll threads of self-work
	misc: fastrpc: Mark all sessions as invalid in cb_remove
	ext4: fix double-free of blocks due to wrong extents moved_len
	tracing: Fix wasted memory in saved_cmdlines logic
	staging: iio: ad5933: fix type mismatch regression
	iio: magnetometer: rm3100: add boundary check for the value read from RM3100_REG_TMRC
	iio: accel: bma400: Fix a compilation problem
	media: rc: bpf attach/detach requires write permission
	hv_netvsc: Fix race condition between netvsc_probe and netvsc_remove
	ring-buffer: Clean ring_buffer_poll_wait() error return
	serial: max310x: set default value when reading clock ready bit
	serial: max310x: improve crystal stable clock detection
	x86/Kconfig: Transmeta Crusoe is CPU family 5, not 6
	x86/mm/ident_map: Use gbpages only where full GB page should be mapped.
	mmc: slot-gpio: Allow non-sleeping GPIO ro
	ALSA: hda/conexant: Add quirk for SWS JS201D
	nilfs2: fix data corruption in dsync block recovery for small block sizes
	nilfs2: fix hang in nilfs_lookup_dirty_data_buffers()
	crypto: ccp - Fix null pointer dereference in __sev_platform_shutdown_locked
	nfp: use correct macro for LengthSelect in BAR config
	nfp: flower: prevent re-adding mac index for bonded port
	wifi: mac80211: reload info pointer in ieee80211_tx_dequeue()
	irqchip/irq-brcmstb-l2: Add write memory barrier before exit
	irqchip/gic-v3-its: Fix GICv4.1 VPE affinity update
	s390/qeth: Fix potential loss of L3-IP@ in case of network issues
	ceph: prevent use-after-free in encode_cap_msg()
	of: property: fix typo in io-channels
	can: j1939: Fix UAF in j1939_sk_match_filter during setsockopt(SO_J1939_FILTER)
	pmdomain: core: Move the unused cleanup to a _sync initcall
	tracing: Inform kmemleak of saved_cmdlines allocation
	Revert "md/raid5: Wait for MD_SB_CHANGE_PENDING in raid5d"
	bus: moxtet: Add spi device table
	PCI: dwc: endpoint: Fix dw_pcie_ep_raise_msix_irq() alignment support
	mips: Fix max_mapnr being uninitialized on early stages
	crypto: lib/mpi - Fix unexpected pointer access in mpi_ec_init
	serial: Add rs485_supported to uart_port
	serial: 8250_exar: Fill in rs485_supported
	serial: 8250_exar: Set missing rs485_supported flag
	scripts/decode_stacktrace.sh: silence stderr messages from addr2line/nm
	scripts/decode_stacktrace.sh: support old bash version
	scripts: decode_stacktrace: demangle Rust symbols
	scripts/decode_stacktrace.sh: optionally use LLVM utilities
	netfilter: ipset: fix performance regression in swap operation
	netfilter: ipset: Missing gc cancellations fixed
	hrtimer: Ignore slack time for RT tasks in schedule_hrtimeout_range()
	Revert "arm64: Stash shadow stack pointer in the task struct on interrupt"
	net: prevent mss overflow in skb_segment()
	sched/membarrier: reduce the ability to hammer on sys_membarrier
	nilfs2: fix potential bug in end_buffer_async_write
	nilfs2: replace WARN_ONs for invalid DAT metadata block requests
	dm: limit the number of targets and parameter size area
	PM: runtime: add devm_pm_runtime_enable helper
	PM: runtime: Have devm_pm_runtime_enable() handle pm_runtime_dont_use_autosuspend()
	drm/msm/dsi: Enable runtime PM
	netfilter: nf_tables: fix pointer math issue in nft_byteorder_eval()
	net: bcmgenet: Fix EEE implementation
	PCI: dwc: Fix a 64bit bug in dw_pcie_ep_raise_msix_irq()
	Linux 5.10.210

Change-Id: I5e7327f58dd6abd26ac2b1e328a81c1010d1147c
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2024-04-10 07:10:03 +00:00

2046 lines
50 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* drivers/base/power/main.c - Where the driver meets power management.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Lab
*
* The driver model core calls device_pm_add() when a device is registered.
* This will initialize the embedded device_pm_info object in the device
* and add it to the list of power-controlled devices. sysfs entries for
* controlling device power management will also be added.
*
* A separate list is used for keeping track of power info, because the power
* domain dependencies may differ from the ancestral dependencies that the
* subsystem list maintains.
*/
#define pr_fmt(fmt) "PM: " fmt
#define dev_fmt pr_fmt
#include <linux/device.h>
#include <linux/export.h>
#include <linux/mutex.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/pm-trace.h>
#include <linux/pm_wakeirq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/async.h>
#include <linux/suspend.h>
#include <trace/events/power.h>
#include <linux/cpufreq.h>
#include <linux/cpuidle.h>
#include <linux/devfreq.h>
#include <linux/timer.h>
#include <linux/wakeup_reason.h>
#include "../base.h"
#include "power.h"
typedef int (*pm_callback_t)(struct device *);
#define list_for_each_entry_rcu_locked(pos, head, member) \
list_for_each_entry_rcu(pos, head, member, \
device_links_read_lock_held())
/*
* The entries in the dpm_list list are in a depth first order, simply
* because children are guaranteed to be discovered after parents, and
* are inserted at the back of the list on discovery.
*
* Since device_pm_add() may be called with a device lock held,
* we must never try to acquire a device lock while holding
* dpm_list_mutex.
*/
LIST_HEAD(dpm_list);
static LIST_HEAD(dpm_prepared_list);
static LIST_HEAD(dpm_suspended_list);
static LIST_HEAD(dpm_late_early_list);
static LIST_HEAD(dpm_noirq_list);
struct suspend_stats suspend_stats;
static DEFINE_MUTEX(dpm_list_mtx);
static pm_message_t pm_transition;
static int async_error;
static const char *pm_verb(int event)
{
switch (event) {
case PM_EVENT_SUSPEND:
return "suspend";
case PM_EVENT_RESUME:
return "resume";
case PM_EVENT_FREEZE:
return "freeze";
case PM_EVENT_QUIESCE:
return "quiesce";
case PM_EVENT_HIBERNATE:
return "hibernate";
case PM_EVENT_THAW:
return "thaw";
case PM_EVENT_RESTORE:
return "restore";
case PM_EVENT_RECOVER:
return "recover";
default:
return "(unknown PM event)";
}
}
/**
* device_pm_sleep_init - Initialize system suspend-related device fields.
* @dev: Device object being initialized.
*/
void device_pm_sleep_init(struct device *dev)
{
dev->power.is_prepared = false;
dev->power.is_suspended = false;
dev->power.is_noirq_suspended = false;
dev->power.is_late_suspended = false;
init_completion(&dev->power.completion);
complete_all(&dev->power.completion);
dev->power.wakeup = NULL;
INIT_LIST_HEAD(&dev->power.entry);
}
/**
* device_pm_lock - Lock the list of active devices used by the PM core.
*/
void device_pm_lock(void)
{
mutex_lock(&dpm_list_mtx);
}
/**
* device_pm_unlock - Unlock the list of active devices used by the PM core.
*/
void device_pm_unlock(void)
{
mutex_unlock(&dpm_list_mtx);
}
/**
* device_pm_add - Add a device to the PM core's list of active devices.
* @dev: Device to add to the list.
*/
void device_pm_add(struct device *dev)
{
/* Skip PM setup/initialization. */
if (device_pm_not_required(dev))
return;
pr_debug("Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
device_pm_check_callbacks(dev);
mutex_lock(&dpm_list_mtx);
if (dev->parent && dev->parent->power.is_prepared)
dev_warn(dev, "parent %s should not be sleeping\n",
dev_name(dev->parent));
list_add_tail(&dev->power.entry, &dpm_list);
dev->power.in_dpm_list = true;
mutex_unlock(&dpm_list_mtx);
}
/**
* device_pm_remove - Remove a device from the PM core's list of active devices.
* @dev: Device to be removed from the list.
*/
void device_pm_remove(struct device *dev)
{
if (device_pm_not_required(dev))
return;
pr_debug("Removing info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
complete_all(&dev->power.completion);
mutex_lock(&dpm_list_mtx);
list_del_init(&dev->power.entry);
dev->power.in_dpm_list = false;
mutex_unlock(&dpm_list_mtx);
device_wakeup_disable(dev);
pm_runtime_remove(dev);
device_pm_check_callbacks(dev);
}
/**
* device_pm_move_before - Move device in the PM core's list of active devices.
* @deva: Device to move in dpm_list.
* @devb: Device @deva should come before.
*/
void device_pm_move_before(struct device *deva, struct device *devb)
{
pr_debug("Moving %s:%s before %s:%s\n",
deva->bus ? deva->bus->name : "No Bus", dev_name(deva),
devb->bus ? devb->bus->name : "No Bus", dev_name(devb));
/* Delete deva from dpm_list and reinsert before devb. */
list_move_tail(&deva->power.entry, &devb->power.entry);
}
/**
* device_pm_move_after - Move device in the PM core's list of active devices.
* @deva: Device to move in dpm_list.
* @devb: Device @deva should come after.
*/
void device_pm_move_after(struct device *deva, struct device *devb)
{
pr_debug("Moving %s:%s after %s:%s\n",
deva->bus ? deva->bus->name : "No Bus", dev_name(deva),
devb->bus ? devb->bus->name : "No Bus", dev_name(devb));
/* Delete deva from dpm_list and reinsert after devb. */
list_move(&deva->power.entry, &devb->power.entry);
}
/**
* device_pm_move_last - Move device to end of the PM core's list of devices.
* @dev: Device to move in dpm_list.
*/
void device_pm_move_last(struct device *dev)
{
pr_debug("Moving %s:%s to end of list\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
list_move_tail(&dev->power.entry, &dpm_list);
}
static ktime_t initcall_debug_start(struct device *dev, void *cb)
{
if (!pm_print_times_enabled)
return 0;
dev_info(dev, "calling %pS @ %i, parent: %s\n", cb,
task_pid_nr(current),
dev->parent ? dev_name(dev->parent) : "none");
return ktime_get();
}
static void initcall_debug_report(struct device *dev, ktime_t calltime,
void *cb, int error)
{
ktime_t rettime;
s64 nsecs;
if (!pm_print_times_enabled)
return;
rettime = ktime_get();
nsecs = (s64) ktime_to_ns(ktime_sub(rettime, calltime));
dev_info(dev, "%pS returned %d after %Ld usecs\n", cb, error,
(unsigned long long)nsecs >> 10);
}
/**
* dpm_wait - Wait for a PM operation to complete.
* @dev: Device to wait for.
* @async: If unset, wait only if the device's power.async_suspend flag is set.
*/
static void dpm_wait(struct device *dev, bool async)
{
if (!dev)
return;
if (async || (pm_async_enabled && dev->power.async_suspend))
wait_for_completion(&dev->power.completion);
}
static int dpm_wait_fn(struct device *dev, void *async_ptr)
{
dpm_wait(dev, *((bool *)async_ptr));
return 0;
}
static void dpm_wait_for_children(struct device *dev, bool async)
{
device_for_each_child(dev, &async, dpm_wait_fn);
}
static void dpm_wait_for_suppliers(struct device *dev, bool async)
{
struct device_link *link;
int idx;
idx = device_links_read_lock();
/*
* If the supplier goes away right after we've checked the link to it,
* we'll wait for its completion to change the state, but that's fine,
* because the only things that will block as a result are the SRCU
* callbacks freeing the link objects for the links in the list we're
* walking.
*/
list_for_each_entry_rcu_locked(link, &dev->links.suppliers, c_node)
if (READ_ONCE(link->status) != DL_STATE_DORMANT)
dpm_wait(link->supplier, async);
device_links_read_unlock(idx);
}
static bool dpm_wait_for_superior(struct device *dev, bool async)
{
struct device *parent;
/*
* If the device is resumed asynchronously and the parent's callback
* deletes both the device and the parent itself, the parent object may
* be freed while this function is running, so avoid that by reference
* counting the parent once more unless the device has been deleted
* already (in which case return right away).
*/
mutex_lock(&dpm_list_mtx);
if (!device_pm_initialized(dev)) {
mutex_unlock(&dpm_list_mtx);
return false;
}
parent = get_device(dev->parent);
mutex_unlock(&dpm_list_mtx);
dpm_wait(parent, async);
put_device(parent);
dpm_wait_for_suppliers(dev, async);
/*
* If the parent's callback has deleted the device, attempting to resume
* it would be invalid, so avoid doing that then.
*/
return device_pm_initialized(dev);
}
static void dpm_wait_for_consumers(struct device *dev, bool async)
{
struct device_link *link;
int idx;
idx = device_links_read_lock();
/*
* The status of a device link can only be changed from "dormant" by a
* probe, but that cannot happen during system suspend/resume. In
* theory it can change to "dormant" at that time, but then it is
* reasonable to wait for the target device anyway (eg. if it goes
* away, it's better to wait for it to go away completely and then
* continue instead of trying to continue in parallel with its
* unregistration).
*/
list_for_each_entry_rcu_locked(link, &dev->links.consumers, s_node)
if (READ_ONCE(link->status) != DL_STATE_DORMANT)
dpm_wait(link->consumer, async);
device_links_read_unlock(idx);
}
static void dpm_wait_for_subordinate(struct device *dev, bool async)
{
dpm_wait_for_children(dev, async);
dpm_wait_for_consumers(dev, async);
}
/**
* pm_op - Return the PM operation appropriate for given PM event.
* @ops: PM operations to choose from.
* @state: PM transition of the system being carried out.
*/
static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
{
switch (state.event) {
#ifdef CONFIG_SUSPEND
case PM_EVENT_SUSPEND:
return ops->suspend;
case PM_EVENT_RESUME:
return ops->resume;
#endif /* CONFIG_SUSPEND */
#ifdef CONFIG_HIBERNATE_CALLBACKS
case PM_EVENT_FREEZE:
case PM_EVENT_QUIESCE:
return ops->freeze;
case PM_EVENT_HIBERNATE:
return ops->poweroff;
case PM_EVENT_THAW:
case PM_EVENT_RECOVER:
return ops->thaw;
case PM_EVENT_RESTORE:
return ops->restore;
#endif /* CONFIG_HIBERNATE_CALLBACKS */
}
return NULL;
}
/**
* pm_late_early_op - Return the PM operation appropriate for given PM event.
* @ops: PM operations to choose from.
* @state: PM transition of the system being carried out.
*
* Runtime PM is disabled for @dev while this function is being executed.
*/
static pm_callback_t pm_late_early_op(const struct dev_pm_ops *ops,
pm_message_t state)
{
switch (state.event) {
#ifdef CONFIG_SUSPEND
case PM_EVENT_SUSPEND:
return ops->suspend_late;
case PM_EVENT_RESUME:
return ops->resume_early;
#endif /* CONFIG_SUSPEND */
#ifdef CONFIG_HIBERNATE_CALLBACKS
case PM_EVENT_FREEZE:
case PM_EVENT_QUIESCE:
return ops->freeze_late;
case PM_EVENT_HIBERNATE:
return ops->poweroff_late;
case PM_EVENT_THAW:
case PM_EVENT_RECOVER:
return ops->thaw_early;
case PM_EVENT_RESTORE:
return ops->restore_early;
#endif /* CONFIG_HIBERNATE_CALLBACKS */
}
return NULL;
}
/**
* pm_noirq_op - Return the PM operation appropriate for given PM event.
* @ops: PM operations to choose from.
* @state: PM transition of the system being carried out.
*
* The driver of @dev will not receive interrupts while this function is being
* executed.
*/
static pm_callback_t pm_noirq_op(const struct dev_pm_ops *ops, pm_message_t state)
{
switch (state.event) {
#ifdef CONFIG_SUSPEND
case PM_EVENT_SUSPEND:
return ops->suspend_noirq;
case PM_EVENT_RESUME:
return ops->resume_noirq;
#endif /* CONFIG_SUSPEND */
#ifdef CONFIG_HIBERNATE_CALLBACKS
case PM_EVENT_FREEZE:
case PM_EVENT_QUIESCE:
return ops->freeze_noirq;
case PM_EVENT_HIBERNATE:
return ops->poweroff_noirq;
case PM_EVENT_THAW:
case PM_EVENT_RECOVER:
return ops->thaw_noirq;
case PM_EVENT_RESTORE:
return ops->restore_noirq;
#endif /* CONFIG_HIBERNATE_CALLBACKS */
}
return NULL;
}
static void pm_dev_dbg(struct device *dev, pm_message_t state, const char *info)
{
dev_dbg(dev, "%s%s%s\n", info, pm_verb(state.event),
((state.event & PM_EVENT_SLEEP) && device_may_wakeup(dev)) ?
", may wakeup" : "");
}
static void pm_dev_err(struct device *dev, pm_message_t state, const char *info,
int error)
{
dev_err(dev, "failed to %s%s: error %d\n", pm_verb(state.event), info,
error);
}
static void dpm_show_time(ktime_t starttime, pm_message_t state, int error,
const char *info)
{
ktime_t calltime;
u64 usecs64;
int usecs;
calltime = ktime_get();
usecs64 = ktime_to_ns(ktime_sub(calltime, starttime));
do_div(usecs64, NSEC_PER_USEC);
usecs = usecs64;
if (usecs == 0)
usecs = 1;
pm_pr_dbg("%s%s%s of devices %s after %ld.%03ld msecs\n",
info ?: "", info ? " " : "", pm_verb(state.event),
error ? "aborted" : "complete",
usecs / USEC_PER_MSEC, usecs % USEC_PER_MSEC);
}
static int dpm_run_callback(pm_callback_t cb, struct device *dev,
pm_message_t state, const char *info)
{
ktime_t calltime;
int error;
if (!cb)
return 0;
calltime = initcall_debug_start(dev, cb);
pm_dev_dbg(dev, state, info);
trace_device_pm_callback_start(dev, info, state.event);
error = cb(dev);
trace_device_pm_callback_end(dev, error);
suspend_report_result(cb, error);
initcall_debug_report(dev, calltime, cb, error);
return error;
}
#ifdef CONFIG_DPM_WATCHDOG
struct dpm_watchdog {
struct device *dev;
struct task_struct *tsk;
struct timer_list timer;
};
#define DECLARE_DPM_WATCHDOG_ON_STACK(wd) \
struct dpm_watchdog wd
/**
* dpm_watchdog_handler - Driver suspend / resume watchdog handler.
* @t: The timer that PM watchdog depends on.
*
* Called when a driver has timed out suspending or resuming.
* There's not much we can do here to recover so panic() to
* capture a crash-dump in pstore.
*/
static void dpm_watchdog_handler(struct timer_list *t)
{
struct dpm_watchdog *wd = from_timer(wd, t, timer);
dev_emerg(wd->dev, "**** DPM device timeout ****\n");
show_stack(wd->tsk, NULL, KERN_EMERG);
panic("%s %s: unrecoverable failure\n",
dev_driver_string(wd->dev), dev_name(wd->dev));
}
/**
* dpm_watchdog_set - Enable pm watchdog for given device.
* @wd: Watchdog. Must be allocated on the stack.
* @dev: Device to handle.
*/
static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev)
{
struct timer_list *timer = &wd->timer;
wd->dev = dev;
wd->tsk = current;
timer_setup_on_stack(timer, dpm_watchdog_handler, 0);
/* use same timeout value for both suspend and resume */
timer->expires = jiffies + HZ * CONFIG_DPM_WATCHDOG_TIMEOUT;
add_timer(timer);
}
/**
* dpm_watchdog_clear - Disable suspend/resume watchdog.
* @wd: Watchdog to disable.
*/
static void dpm_watchdog_clear(struct dpm_watchdog *wd)
{
struct timer_list *timer = &wd->timer;
del_timer_sync(timer);
destroy_timer_on_stack(timer);
}
#else
#define DECLARE_DPM_WATCHDOG_ON_STACK(wd)
#define dpm_watchdog_set(x, y)
#define dpm_watchdog_clear(x)
#endif
/*------------------------- Resume routines -------------------------*/
/**
* dev_pm_skip_resume - System-wide device resume optimization check.
* @dev: Target device.
*
* Return:
* - %false if the transition under way is RESTORE.
* - Return value of dev_pm_skip_suspend() if the transition under way is THAW.
* - The logical negation of %power.must_resume otherwise (that is, when the
* transition under way is RESUME).
*/
bool dev_pm_skip_resume(struct device *dev)
{
if (pm_transition.event == PM_EVENT_RESTORE)
return false;
if (pm_transition.event == PM_EVENT_THAW)
return dev_pm_skip_suspend(dev);
return !dev->power.must_resume;
}
/**
* __device_resume_noirq - Execute a "noirq resume" callback for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
* @async: If true, the device is being resumed asynchronously.
*
* The driver of @dev will not receive interrupts while this function is being
* executed.
*/
static void __device_resume_noirq(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
bool skip_resume;
int error = 0;
TRACE_DEVICE(dev);
TRACE_RESUME(0);
if (dev->power.syscore || dev->power.direct_complete)
goto Out;
if (!dev->power.is_noirq_suspended)
goto Out;
if (!dpm_wait_for_superior(dev, async))
goto Out;
skip_resume = dev_pm_skip_resume(dev);
/*
* If the driver callback is skipped below or by the middle layer
* callback and device_resume_early() also skips the driver callback for
* this device later, it needs to appear as "suspended" to PM-runtime,
* so change its status accordingly.
*
* Otherwise, the device is going to be resumed, so set its PM-runtime
* status to "active", but do that only if DPM_FLAG_SMART_SUSPEND is set
* to avoid confusing drivers that don't use it.
*/
if (skip_resume)
pm_runtime_set_suspended(dev);
else if (dev_pm_skip_suspend(dev))
pm_runtime_set_active(dev);
if (dev->pm_domain) {
info = "noirq power domain ";
callback = pm_noirq_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
info = "noirq type ";
callback = pm_noirq_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
info = "noirq class ";
callback = pm_noirq_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
info = "noirq bus ";
callback = pm_noirq_op(dev->bus->pm, state);
}
if (callback)
goto Run;
if (skip_resume)
goto Skip;
if (dev->driver && dev->driver->pm) {
info = "noirq driver ";
callback = pm_noirq_op(dev->driver->pm, state);
}
Run:
error = dpm_run_callback(callback, dev, state, info);
Skip:
dev->power.is_noirq_suspended = false;
Out:
complete_all(&dev->power.completion);
TRACE_RESUME(error);
if (error) {
suspend_stats.failed_resume_noirq++;
dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
}
}
static bool is_async(struct device *dev)
{
return dev->power.async_suspend && pm_async_enabled
&& !pm_trace_is_enabled();
}
static bool dpm_async_fn(struct device *dev, async_func_t func)
{
reinit_completion(&dev->power.completion);
if (!is_async(dev))
return false;
get_device(dev);
if (async_schedule_dev_nocall(func, dev))
return true;
put_device(dev);
return false;
}
static void async_resume_noirq(void *data, async_cookie_t cookie)
{
struct device *dev = data;
__device_resume_noirq(dev, pm_transition, true);
put_device(dev);
}
static void device_resume_noirq(struct device *dev)
{
if (dpm_async_fn(dev, async_resume_noirq))
return;
__device_resume_noirq(dev, pm_transition, false);
}
static void dpm_noirq_resume_devices(pm_message_t state)
{
struct device *dev;
ktime_t starttime = ktime_get();
trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, true);
mutex_lock(&dpm_list_mtx);
pm_transition = state;
while (!list_empty(&dpm_noirq_list)) {
dev = to_device(dpm_noirq_list.next);
get_device(dev);
list_move_tail(&dev->power.entry, &dpm_late_early_list);
mutex_unlock(&dpm_list_mtx);
device_resume_noirq(dev);
put_device(dev);
mutex_lock(&dpm_list_mtx);
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
dpm_show_time(starttime, state, 0, "noirq");
trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
}
/**
* dpm_resume_noirq - Execute "noirq resume" callbacks for all devices.
* @state: PM transition of the system being carried out.
*
* Invoke the "noirq" resume callbacks for all devices in dpm_noirq_list and
* allow device drivers' interrupt handlers to be called.
*/
void dpm_resume_noirq(pm_message_t state)
{
dpm_noirq_resume_devices(state);
resume_device_irqs();
device_wakeup_disarm_wake_irqs();
cpuidle_resume();
}
/**
* __device_resume_early - Execute an "early resume" callback for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
* @async: If true, the device is being resumed asynchronously.
*
* Runtime PM is disabled for @dev while this function is being executed.
*/
static void __device_resume_early(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
TRACE_DEVICE(dev);
TRACE_RESUME(0);
if (dev->power.syscore || dev->power.direct_complete)
goto Out;
if (!dev->power.is_late_suspended)
goto Out;
if (!dpm_wait_for_superior(dev, async))
goto Out;
if (dev->pm_domain) {
info = "early power domain ";
callback = pm_late_early_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
info = "early type ";
callback = pm_late_early_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
info = "early class ";
callback = pm_late_early_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
info = "early bus ";
callback = pm_late_early_op(dev->bus->pm, state);
}
if (callback)
goto Run;
if (dev_pm_skip_resume(dev))
goto Skip;
if (dev->driver && dev->driver->pm) {
info = "early driver ";
callback = pm_late_early_op(dev->driver->pm, state);
}
Run:
error = dpm_run_callback(callback, dev, state, info);
Skip:
dev->power.is_late_suspended = false;
Out:
TRACE_RESUME(error);
pm_runtime_enable(dev);
complete_all(&dev->power.completion);
if (error) {
suspend_stats.failed_resume_early++;
dpm_save_failed_step(SUSPEND_RESUME_EARLY);
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async early" : " early", error);
}
}
static void async_resume_early(void *data, async_cookie_t cookie)
{
struct device *dev = data;
__device_resume_early(dev, pm_transition, true);
put_device(dev);
}
static void device_resume_early(struct device *dev)
{
if (dpm_async_fn(dev, async_resume_early))
return;
__device_resume_early(dev, pm_transition, false);
}
/**
* dpm_resume_early - Execute "early resume" callbacks for all devices.
* @state: PM transition of the system being carried out.
*/
void dpm_resume_early(pm_message_t state)
{
struct device *dev;
ktime_t starttime = ktime_get();
trace_suspend_resume(TPS("dpm_resume_early"), state.event, true);
mutex_lock(&dpm_list_mtx);
pm_transition = state;
while (!list_empty(&dpm_late_early_list)) {
dev = to_device(dpm_late_early_list.next);
get_device(dev);
list_move_tail(&dev->power.entry, &dpm_suspended_list);
mutex_unlock(&dpm_list_mtx);
device_resume_early(dev);
put_device(dev);
mutex_lock(&dpm_list_mtx);
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
dpm_show_time(starttime, state, 0, "early");
trace_suspend_resume(TPS("dpm_resume_early"), state.event, false);
}
/**
* dpm_resume_start - Execute "noirq" and "early" device callbacks.
* @state: PM transition of the system being carried out.
*/
void dpm_resume_start(pm_message_t state)
{
dpm_resume_noirq(state);
dpm_resume_early(state);
}
EXPORT_SYMBOL_GPL(dpm_resume_start);
/**
* __device_resume - Execute "resume" callbacks for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
* @async: If true, the device is being resumed asynchronously.
*/
static void __device_resume(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
DECLARE_DPM_WATCHDOG_ON_STACK(wd);
TRACE_DEVICE(dev);
TRACE_RESUME(0);
if (dev->power.syscore)
goto Complete;
if (dev->power.direct_complete) {
/* Match the pm_runtime_disable() in __device_suspend(). */
pm_runtime_enable(dev);
goto Complete;
}
if (!dpm_wait_for_superior(dev, async))
goto Complete;
dpm_watchdog_set(&wd, dev);
device_lock(dev);
/*
* This is a fib. But we'll allow new children to be added below
* a resumed device, even if the device hasn't been completed yet.
*/
dev->power.is_prepared = false;
if (!dev->power.is_suspended)
goto Unlock;
if (dev->pm_domain) {
info = "power domain ";
callback = pm_op(&dev->pm_domain->ops, state);
goto Driver;
}
if (dev->type && dev->type->pm) {
info = "type ";
callback = pm_op(dev->type->pm, state);
goto Driver;
}
if (dev->class && dev->class->pm) {
info = "class ";
callback = pm_op(dev->class->pm, state);
goto Driver;
}
if (dev->bus) {
if (dev->bus->pm) {
info = "bus ";
callback = pm_op(dev->bus->pm, state);
} else if (dev->bus->resume) {
info = "legacy bus ";
callback = dev->bus->resume;
goto End;
}
}
Driver:
if (!callback && dev->driver && dev->driver->pm) {
info = "driver ";
callback = pm_op(dev->driver->pm, state);
}
End:
error = dpm_run_callback(callback, dev, state, info);
dev->power.is_suspended = false;
Unlock:
device_unlock(dev);
dpm_watchdog_clear(&wd);
Complete:
complete_all(&dev->power.completion);
TRACE_RESUME(error);
if (error) {
suspend_stats.failed_resume++;
dpm_save_failed_step(SUSPEND_RESUME);
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async" : "", error);
}
}
static void async_resume(void *data, async_cookie_t cookie)
{
struct device *dev = data;
__device_resume(dev, pm_transition, true);
put_device(dev);
}
static void device_resume(struct device *dev)
{
if (dpm_async_fn(dev, async_resume))
return;
__device_resume(dev, pm_transition, false);
}
/**
* dpm_resume - Execute "resume" callbacks for non-sysdev devices.
* @state: PM transition of the system being carried out.
*
* Execute the appropriate "resume" callback for all devices whose status
* indicates that they are suspended.
*/
void dpm_resume(pm_message_t state)
{
struct device *dev;
ktime_t starttime = ktime_get();
trace_suspend_resume(TPS("dpm_resume"), state.event, true);
might_sleep();
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_suspended_list)) {
dev = to_device(dpm_suspended_list.next);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
device_resume(dev);
mutex_lock(&dpm_list_mtx);
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &dpm_prepared_list);
mutex_unlock(&dpm_list_mtx);
put_device(dev);
mutex_lock(&dpm_list_mtx);
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
dpm_show_time(starttime, state, 0, NULL);
cpufreq_resume();
devfreq_resume();
trace_suspend_resume(TPS("dpm_resume"), state.event, false);
}
/**
* device_complete - Complete a PM transition for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
*/
static void device_complete(struct device *dev, pm_message_t state)
{
void (*callback)(struct device *) = NULL;
const char *info = NULL;
if (dev->power.syscore)
goto out;
device_lock(dev);
if (dev->pm_domain) {
info = "completing power domain ";
callback = dev->pm_domain->ops.complete;
} else if (dev->type && dev->type->pm) {
info = "completing type ";
callback = dev->type->pm->complete;
} else if (dev->class && dev->class->pm) {
info = "completing class ";
callback = dev->class->pm->complete;
} else if (dev->bus && dev->bus->pm) {
info = "completing bus ";
callback = dev->bus->pm->complete;
}
if (!callback && dev->driver && dev->driver->pm) {
info = "completing driver ";
callback = dev->driver->pm->complete;
}
if (callback) {
pm_dev_dbg(dev, state, info);
callback(dev);
}
device_unlock(dev);
out:
pm_runtime_put(dev);
}
/**
* dpm_complete - Complete a PM transition for all non-sysdev devices.
* @state: PM transition of the system being carried out.
*
* Execute the ->complete() callbacks for all devices whose PM status is not
* DPM_ON (this allows new devices to be registered).
*/
void dpm_complete(pm_message_t state)
{
struct list_head list;
trace_suspend_resume(TPS("dpm_complete"), state.event, true);
might_sleep();
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
dev->power.is_prepared = false;
list_move(&dev->power.entry, &list);
mutex_unlock(&dpm_list_mtx);
trace_device_pm_callback_start(dev, "", state.event);
device_complete(dev, state);
trace_device_pm_callback_end(dev, 0);
put_device(dev);
mutex_lock(&dpm_list_mtx);
}
list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
/* Allow device probing and trigger re-probing of deferred devices */
device_unblock_probing();
trace_suspend_resume(TPS("dpm_complete"), state.event, false);
}
/**
* dpm_resume_end - Execute "resume" callbacks and complete system transition.
* @state: PM transition of the system being carried out.
*
* Execute "resume" callbacks for all devices and complete the PM transition of
* the system.
*/
void dpm_resume_end(pm_message_t state)
{
dpm_resume(state);
dpm_complete(state);
}
EXPORT_SYMBOL_GPL(dpm_resume_end);
/*------------------------- Suspend routines -------------------------*/
/**
* resume_event - Return a "resume" message for given "suspend" sleep state.
* @sleep_state: PM message representing a sleep state.
*
* Return a PM message representing the resume event corresponding to given
* sleep state.
*/
static pm_message_t resume_event(pm_message_t sleep_state)
{
switch (sleep_state.event) {
case PM_EVENT_SUSPEND:
return PMSG_RESUME;
case PM_EVENT_FREEZE:
case PM_EVENT_QUIESCE:
return PMSG_RECOVER;
case PM_EVENT_HIBERNATE:
return PMSG_RESTORE;
}
return PMSG_ON;
}
static void dpm_superior_set_must_resume(struct device *dev)
{
struct device_link *link;
int idx;
if (dev->parent)
dev->parent->power.must_resume = true;
idx = device_links_read_lock();
list_for_each_entry_rcu_locked(link, &dev->links.suppliers, c_node)
link->supplier->power.must_resume = true;
device_links_read_unlock(idx);
}
/**
* __device_suspend_noirq - Execute a "noirq suspend" callback for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
* @async: If true, the device is being suspended asynchronously.
*
* The driver of @dev will not receive interrupts while this function is being
* executed.
*/
static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
TRACE_DEVICE(dev);
TRACE_SUSPEND(0);
dpm_wait_for_subordinate(dev, async);
if (async_error)
goto Complete;
if (dev->power.syscore || dev->power.direct_complete)
goto Complete;
if (dev->pm_domain) {
info = "noirq power domain ";
callback = pm_noirq_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
info = "noirq type ";
callback = pm_noirq_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
info = "noirq class ";
callback = pm_noirq_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
info = "noirq bus ";
callback = pm_noirq_op(dev->bus->pm, state);
}
if (callback)
goto Run;
if (dev_pm_skip_suspend(dev))
goto Skip;
if (dev->driver && dev->driver->pm) {
info = "noirq driver ";
callback = pm_noirq_op(dev->driver->pm, state);
}
Run:
error = dpm_run_callback(callback, dev, state, info);
if (error) {
async_error = error;
log_suspend_abort_reason("Device %s failed to %s noirq: error %d",
dev_name(dev), pm_verb(state.event), error);
goto Complete;
}
Skip:
dev->power.is_noirq_suspended = true;
/*
* Skipping the resume of devices that were in use right before the
* system suspend (as indicated by their PM-runtime usage counters)
* would be suboptimal. Also resume them if doing that is not allowed
* to be skipped.
*/
if (atomic_read(&dev->power.usage_count) > 1 ||
!(dev_pm_test_driver_flags(dev, DPM_FLAG_MAY_SKIP_RESUME) &&
dev->power.may_skip_resume))
dev->power.must_resume = true;
if (dev->power.must_resume)
dpm_superior_set_must_resume(dev);
Complete:
complete_all(&dev->power.completion);
TRACE_SUSPEND(error);
return error;
}
static void async_suspend_noirq(void *data, async_cookie_t cookie)
{
struct device *dev = data;
int error;
error = __device_suspend_noirq(dev, pm_transition, true);
if (error) {
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, pm_transition, " async", error);
}
put_device(dev);
}
static int device_suspend_noirq(struct device *dev)
{
if (dpm_async_fn(dev, async_suspend_noirq))
return 0;
return __device_suspend_noirq(dev, pm_transition, false);
}
static int dpm_noirq_suspend_devices(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_late_early_list)) {
struct device *dev = to_device(dpm_late_early_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend_noirq(dev);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, " noirq", error);
dpm_save_failed_dev(dev_name(dev));
} else if (!list_empty(&dev->power.entry)) {
list_move(&dev->power.entry, &dpm_noirq_list);
}
mutex_unlock(&dpm_list_mtx);
put_device(dev);
mutex_lock(&dpm_list_mtx);
if (error || async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
if (!error)
error = async_error;
if (error) {
suspend_stats.failed_suspend_noirq++;
dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
}
dpm_show_time(starttime, state, error, "noirq");
trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, false);
return error;
}
/**
* dpm_suspend_noirq - Execute "noirq suspend" callbacks for all devices.
* @state: PM transition of the system being carried out.
*
* Prevent device drivers' interrupt handlers from being called and invoke
* "noirq" suspend callbacks for all non-sysdev devices.
*/
int dpm_suspend_noirq(pm_message_t state)
{
int ret;
cpuidle_pause();
device_wakeup_arm_wake_irqs();
suspend_device_irqs();
ret = dpm_noirq_suspend_devices(state);
if (ret)
dpm_resume_noirq(resume_event(state));
return ret;
}
static void dpm_propagate_wakeup_to_parent(struct device *dev)
{
struct device *parent = dev->parent;
if (!parent)
return;
spin_lock_irq(&parent->power.lock);
if (dev->power.wakeup_path && !parent->power.ignore_children)
parent->power.wakeup_path = true;
spin_unlock_irq(&parent->power.lock);
}
/**
* __device_suspend_late - Execute a "late suspend" callback for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
* @async: If true, the device is being suspended asynchronously.
*
* Runtime PM is disabled for @dev while this function is being executed.
*/
static int __device_suspend_late(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
TRACE_DEVICE(dev);
TRACE_SUSPEND(0);
__pm_runtime_disable(dev, false);
dpm_wait_for_subordinate(dev, async);
if (async_error)
goto Complete;
if (pm_wakeup_pending()) {
async_error = -EBUSY;
goto Complete;
}
if (dev->power.syscore || dev->power.direct_complete)
goto Complete;
if (dev->pm_domain) {
info = "late power domain ";
callback = pm_late_early_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
info = "late type ";
callback = pm_late_early_op(dev->type->pm, state);
} else if (dev->class && dev->class->pm) {
info = "late class ";
callback = pm_late_early_op(dev->class->pm, state);
} else if (dev->bus && dev->bus->pm) {
info = "late bus ";
callback = pm_late_early_op(dev->bus->pm, state);
}
if (callback)
goto Run;
if (dev_pm_skip_suspend(dev))
goto Skip;
if (dev->driver && dev->driver->pm) {
info = "late driver ";
callback = pm_late_early_op(dev->driver->pm, state);
}
Run:
error = dpm_run_callback(callback, dev, state, info);
if (error) {
async_error = error;
log_suspend_abort_reason("Device %s failed to %s late: error %d",
dev_name(dev), pm_verb(state.event), error);
goto Complete;
}
dpm_propagate_wakeup_to_parent(dev);
Skip:
dev->power.is_late_suspended = true;
Complete:
TRACE_SUSPEND(error);
complete_all(&dev->power.completion);
return error;
}
static void async_suspend_late(void *data, async_cookie_t cookie)
{
struct device *dev = data;
int error;
error = __device_suspend_late(dev, pm_transition, true);
if (error) {
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, pm_transition, " async", error);
}
put_device(dev);
}
static int device_suspend_late(struct device *dev)
{
if (dpm_async_fn(dev, async_suspend_late))
return 0;
return __device_suspend_late(dev, pm_transition, false);
}
/**
* dpm_suspend_late - Execute "late suspend" callbacks for all devices.
* @state: PM transition of the system being carried out.
*/
int dpm_suspend_late(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
trace_suspend_resume(TPS("dpm_suspend_late"), state.event, true);
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_suspended_list)) {
struct device *dev = to_device(dpm_suspended_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend_late(dev);
mutex_lock(&dpm_list_mtx);
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_late_early_list);
if (error) {
pm_dev_err(dev, state, " late", error);
dpm_save_failed_dev(dev_name(dev));
}
mutex_unlock(&dpm_list_mtx);
put_device(dev);
mutex_lock(&dpm_list_mtx);
if (error || async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
if (!error)
error = async_error;
if (error) {
suspend_stats.failed_suspend_late++;
dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
dpm_resume_early(resume_event(state));
}
dpm_show_time(starttime, state, error, "late");
trace_suspend_resume(TPS("dpm_suspend_late"), state.event, false);
return error;
}
/**
* dpm_suspend_end - Execute "late" and "noirq" device suspend callbacks.
* @state: PM transition of the system being carried out.
*/
int dpm_suspend_end(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error;
error = dpm_suspend_late(state);
if (error)
goto out;
error = dpm_suspend_noirq(state);
if (error)
dpm_resume_early(resume_event(state));
out:
dpm_show_time(starttime, state, error, "end");
return error;
}
EXPORT_SYMBOL_GPL(dpm_suspend_end);
/**
* legacy_suspend - Execute a legacy (bus or class) suspend callback for device.
* @dev: Device to suspend.
* @state: PM transition of the system being carried out.
* @cb: Suspend callback to execute.
* @info: string description of caller.
*/
static int legacy_suspend(struct device *dev, pm_message_t state,
int (*cb)(struct device *dev, pm_message_t state),
const char *info)
{
int error;
ktime_t calltime;
calltime = initcall_debug_start(dev, cb);
trace_device_pm_callback_start(dev, info, state.event);
error = cb(dev, state);
trace_device_pm_callback_end(dev, error);
suspend_report_result(cb, error);
initcall_debug_report(dev, calltime, cb, error);
return error;
}
static void dpm_clear_superiors_direct_complete(struct device *dev)
{
struct device_link *link;
int idx;
if (dev->parent) {
spin_lock_irq(&dev->parent->power.lock);
dev->parent->power.direct_complete = false;
spin_unlock_irq(&dev->parent->power.lock);
}
idx = device_links_read_lock();
list_for_each_entry_rcu_locked(link, &dev->links.suppliers, c_node) {
spin_lock_irq(&link->supplier->power.lock);
link->supplier->power.direct_complete = false;
spin_unlock_irq(&link->supplier->power.lock);
}
device_links_read_unlock(idx);
}
/**
* __device_suspend - Execute "suspend" callbacks for given device.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
* @async: If true, the device is being suspended asynchronously.
*/
static int __device_suspend(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
DECLARE_DPM_WATCHDOG_ON_STACK(wd);
TRACE_DEVICE(dev);
TRACE_SUSPEND(0);
dpm_wait_for_subordinate(dev, async);
if (async_error) {
dev->power.direct_complete = false;
goto Complete;
}
/*
* Wait for possible runtime PM transitions of the device in progress
* to complete and if there's a runtime resume request pending for it,
* resume it before proceeding with invoking the system-wide suspend
* callbacks for it.
*
* If the system-wide suspend callbacks below change the configuration
* of the device, they must disable runtime PM for it or otherwise
* ensure that its runtime-resume callbacks will not be confused by that
* change in case they are invoked going forward.
*/
pm_runtime_barrier(dev);
if (pm_wakeup_pending()) {
dev->power.direct_complete = false;
async_error = -EBUSY;
goto Complete;
}
if (dev->power.syscore)
goto Complete;
/* Avoid direct_complete to let wakeup_path propagate. */
if (device_may_wakeup(dev) || dev->power.wakeup_path)
dev->power.direct_complete = false;
if (dev->power.direct_complete) {
if (pm_runtime_status_suspended(dev)) {
pm_runtime_disable(dev);
if (pm_runtime_status_suspended(dev)) {
pm_dev_dbg(dev, state, "direct-complete ");
goto Complete;
}
pm_runtime_enable(dev);
}
dev->power.direct_complete = false;
}
dev->power.may_skip_resume = true;
dev->power.must_resume = !dev_pm_test_driver_flags(dev, DPM_FLAG_MAY_SKIP_RESUME);
dpm_watchdog_set(&wd, dev);
device_lock(dev);
if (dev->pm_domain) {
info = "power domain ";
callback = pm_op(&dev->pm_domain->ops, state);
goto Run;
}
if (dev->type && dev->type->pm) {
info = "type ";
callback = pm_op(dev->type->pm, state);
goto Run;
}
if (dev->class && dev->class->pm) {
info = "class ";
callback = pm_op(dev->class->pm, state);
goto Run;
}
if (dev->bus) {
if (dev->bus->pm) {
info = "bus ";
callback = pm_op(dev->bus->pm, state);
} else if (dev->bus->suspend) {
pm_dev_dbg(dev, state, "legacy bus ");
error = legacy_suspend(dev, state, dev->bus->suspend,
"legacy bus ");
goto End;
}
}
Run:
if (!callback && dev->driver && dev->driver->pm) {
info = "driver ";
callback = pm_op(dev->driver->pm, state);
}
error = dpm_run_callback(callback, dev, state, info);
End:
if (!error) {
dev->power.is_suspended = true;
if (device_may_wakeup(dev))
dev->power.wakeup_path = true;
dpm_propagate_wakeup_to_parent(dev);
dpm_clear_superiors_direct_complete(dev);
} else {
log_suspend_abort_reason("Device %s failed to %s: error %d",
dev_name(dev), pm_verb(state.event), error);
}
device_unlock(dev);
dpm_watchdog_clear(&wd);
Complete:
if (error)
async_error = error;
complete_all(&dev->power.completion);
TRACE_SUSPEND(error);
return error;
}
static void async_suspend(void *data, async_cookie_t cookie)
{
struct device *dev = data;
int error;
error = __device_suspend(dev, pm_transition, true);
if (error) {
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, pm_transition, " async", error);
}
put_device(dev);
}
static int device_suspend(struct device *dev)
{
if (dpm_async_fn(dev, async_suspend))
return 0;
return __device_suspend(dev, pm_transition, false);
}
/**
* dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices.
* @state: PM transition of the system being carried out.
*/
int dpm_suspend(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
trace_suspend_resume(TPS("dpm_suspend"), state.event, true);
might_sleep();
devfreq_suspend();
cpufreq_suspend();
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend(dev);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
dpm_save_failed_dev(dev_name(dev));
} else if (!list_empty(&dev->power.entry)) {
list_move(&dev->power.entry, &dpm_suspended_list);
}
mutex_unlock(&dpm_list_mtx);
put_device(dev);
mutex_lock(&dpm_list_mtx);
if (error || async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
if (!error)
error = async_error;
if (error) {
suspend_stats.failed_suspend++;
dpm_save_failed_step(SUSPEND_SUSPEND);
}
dpm_show_time(starttime, state, error, NULL);
trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
return error;
}
/**
* device_prepare - Prepare a device for system power transition.
* @dev: Device to handle.
* @state: PM transition of the system being carried out.
*
* Execute the ->prepare() callback(s) for given device. No new children of the
* device may be registered after this function has returned.
*/
static int device_prepare(struct device *dev, pm_message_t state)
{
int (*callback)(struct device *) = NULL;
int ret = 0;
/*
* If a device's parent goes into runtime suspend at the wrong time,
* it won't be possible to resume the device. To prevent this we
* block runtime suspend here, during the prepare phase, and allow
* it again during the complete phase.
*/
pm_runtime_get_noresume(dev);
if (dev->power.syscore)
return 0;
device_lock(dev);
dev->power.wakeup_path = false;
if (dev->power.no_pm_callbacks)
goto unlock;
if (dev->pm_domain)
callback = dev->pm_domain->ops.prepare;
else if (dev->type && dev->type->pm)
callback = dev->type->pm->prepare;
else if (dev->class && dev->class->pm)
callback = dev->class->pm->prepare;
else if (dev->bus && dev->bus->pm)
callback = dev->bus->pm->prepare;
if (!callback && dev->driver && dev->driver->pm)
callback = dev->driver->pm->prepare;
if (callback)
ret = callback(dev);
unlock:
device_unlock(dev);
if (ret < 0) {
suspend_report_result(callback, ret);
pm_runtime_put(dev);
return ret;
}
/*
* A positive return value from ->prepare() means "this device appears
* to be runtime-suspended and its state is fine, so if it really is
* runtime-suspended, you can leave it in that state provided that you
* will do the same thing with all of its descendants". This only
* applies to suspend transitions, however.
*/
spin_lock_irq(&dev->power.lock);
dev->power.direct_complete = state.event == PM_EVENT_SUSPEND &&
(ret > 0 || dev->power.no_pm_callbacks) &&
!dev_pm_test_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
spin_unlock_irq(&dev->power.lock);
return 0;
}
/**
* dpm_prepare - Prepare all non-sysdev devices for a system PM transition.
* @state: PM transition of the system being carried out.
*
* Execute the ->prepare() callback(s) for all devices.
*/
int dpm_prepare(pm_message_t state)
{
int error = 0;
trace_suspend_resume(TPS("dpm_prepare"), state.event, true);
might_sleep();
/*
* Give a chance for the known devices to complete their probes, before
* disable probing of devices. This sync point is important at least
* at boot time + hibernation restore.
*/
wait_for_device_probe();
/*
* It is unsafe if probing of devices will happen during suspend or
* hibernation and system behavior will be unpredictable in this case.
* So, let's prohibit device's probing here and defer their probes
* instead. The normal behavior will be restored in dpm_complete().
*/
device_block_probing();
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list) && !error) {
struct device *dev = to_device(dpm_list.next);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
trace_device_pm_callback_start(dev, "", state.event);
error = device_prepare(dev, state);
trace_device_pm_callback_end(dev, error);
mutex_lock(&dpm_list_mtx);
if (!error) {
dev->power.is_prepared = true;
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &dpm_prepared_list);
} else if (error == -EAGAIN) {
error = 0;
} else {
dev_info(dev, "not prepared for power transition: code %d\n",
error);
log_suspend_abort_reason("Device %s not prepared for power transition: code %d",
dev_name(dev), error);
dpm_save_failed_dev(dev_name(dev));
}
mutex_unlock(&dpm_list_mtx);
put_device(dev);
mutex_lock(&dpm_list_mtx);
}
mutex_unlock(&dpm_list_mtx);
trace_suspend_resume(TPS("dpm_prepare"), state.event, false);
return error;
}
/**
* dpm_suspend_start - Prepare devices for PM transition and suspend them.
* @state: PM transition of the system being carried out.
*
* Prepare all non-sysdev devices for system PM transition and execute "suspend"
* callbacks for them.
*/
int dpm_suspend_start(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error;
error = dpm_prepare(state);
if (error) {
suspend_stats.failed_prepare++;
dpm_save_failed_step(SUSPEND_PREPARE);
} else
error = dpm_suspend(state);
dpm_show_time(starttime, state, error, "start");
return error;
}
EXPORT_SYMBOL_GPL(dpm_suspend_start);
void __suspend_report_result(const char *function, void *fn, int ret)
{
if (ret)
pr_err("%s(): %pS returns %d\n", function, fn, ret);
}
EXPORT_SYMBOL_GPL(__suspend_report_result);
/**
* device_pm_wait_for_dev - Wait for suspend/resume of a device to complete.
* @subordinate: Device that needs to wait for @dev.
* @dev: Device to wait for.
*/
int device_pm_wait_for_dev(struct device *subordinate, struct device *dev)
{
dpm_wait(dev, subordinate->power.async_suspend);
return async_error;
}
EXPORT_SYMBOL_GPL(device_pm_wait_for_dev);
/**
* dpm_for_each_dev - device iterator.
* @data: data for the callback.
* @fn: function to be called for each device.
*
* Iterate over devices in dpm_list, and call @fn for each device,
* passing it @data.
*/
void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
{
struct device *dev;
if (!fn)
return;
device_pm_lock();
list_for_each_entry(dev, &dpm_list, power.entry)
fn(dev, data);
device_pm_unlock();
}
EXPORT_SYMBOL_GPL(dpm_for_each_dev);
static bool pm_ops_is_empty(const struct dev_pm_ops *ops)
{
if (!ops)
return true;
return !ops->prepare &&
!ops->suspend &&
!ops->suspend_late &&
!ops->suspend_noirq &&
!ops->resume_noirq &&
!ops->resume_early &&
!ops->resume &&
!ops->complete;
}
void device_pm_check_callbacks(struct device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->power.lock, flags);
dev->power.no_pm_callbacks =
(!dev->bus || (pm_ops_is_empty(dev->bus->pm) &&
!dev->bus->suspend && !dev->bus->resume)) &&
(!dev->class || pm_ops_is_empty(dev->class->pm)) &&
(!dev->type || pm_ops_is_empty(dev->type->pm)) &&
(!dev->pm_domain || pm_ops_is_empty(&dev->pm_domain->ops)) &&
(!dev->driver || (pm_ops_is_empty(dev->driver->pm) &&
!dev->driver->suspend && !dev->driver->resume));
spin_unlock_irqrestore(&dev->power.lock, flags);
}
bool dev_pm_skip_suspend(struct device *dev)
{
return dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) &&
pm_runtime_status_suspended(dev);
}