Merge 4.19.207 into android-4.19-stable
Linux 4.19.207
s390/bpf: Fix 64-bit subtraction of the -0x80000000 constant
net: renesas: sh_eth: Fix freeing wrong tx descriptor
* ip_gre: validate csum_start only on pull
net/ipv4/ip_gre.c
qlcnic: Remove redundant unlock in qlcnic_pinit_from_rom
* fq_codel: reject silly quantum parameters
include/uapi/linux/pkt_sched.h
net/sched/sch_fq_codel.c
* netfilter: socket: icmp6: fix use-after-scope
net/ipv6/netfilter/nf_socket_ipv6.c
net: dsa: b53: Fix calculating number of switch ports
ARC: export clear_user_page() for modules
mtd: rawnand: cafe: Fix a resource leak in the error handling path of 'cafe_nand_probe()'
* PCI: Sync __pci_register_driver() stub for CONFIG_PCI=n
include/linux/pci.h
* KVM: arm64: Handle PSCI resets before userspace touches vCPU state
virt/kvm/arm/arm.c
* PCI: Fix pci_dev_str_match_path() alloc while atomic bug
drivers/pci/pci.c
mfd: axp20x: Update AXP288 volatile ranges
NTB: perf: Fix an error code in perf_setup_inbuf()
ethtool: Fix an error code in cxgb2.c
block, bfq: honor already-setup queue merges
net: usb: cdc_mbim: avoid altsetting toggling for Telit LN920
* PCI: Add ACS quirks for Cavium multi-function devices
drivers/pci/quirks.c
mfd: Don't use irq_create_mapping() to resolve a mapping
dt-bindings: mtd: gpmc: Fix the ECC bytes vs. OOB bytes equation
KVM: s390: index kvm->arch.idle_mask by vcpu_idx
* mm/memory_hotplug: use "unsigned long" for PFN in zone_for_pfn_range()
include/linux/memory_hotplug.h
mm/memory_hotplug.c
net: hns3: pad the short tunnel frame before sending to hardware
ibmvnic: check failover_pending in login response
qed: Handle management FW error
* tcp: fix tp->undo_retrans accounting in tcp_sacktag_one()
net/ipv4/tcp_input.c
* net: dsa: destroy the phylink instance on any error in dsa_slave_phy_setup
net/dsa/slave.c
* net/af_unix: fix a data-race in unix_dgram_poll
include/linux/skbuff.h
net/unix/af_unix.c
* events: Reuse value read using READ_ONCE instead of re-reading it
kernel/events/core.c
net/mlx5: Fix potential sleeping in atomic context
* perf machine: Initialize srcline string member in add_location struct
tools/perf/util/machine.c
* tipc: increase timeout in tipc_sk_enqueue()
net/tipc/socket.c
r6040: Restore MDIO clock frequency after MAC reset
* net/l2tp: Fix reference count leak in l2tp_udp_recv_core
net/l2tp/l2tp_core.c
* dccp: don't duplicate ccid when cloning dccp sock
net/dccp/minisocks.c
ptp: dp83640: don't define PAGE0
* net-caif: avoid user-triggerable WARN_ON(1)
net/caif/chnl_net.c
* tipc: fix an use-after-free issue in tipc_recvmsg
net/tipc/socket.c
x86/mm: Fix kern_addr_valid() to cope with existing but not present entries
* PCI: Add AMD GPU multi-function power dependencies
drivers/pci/quirks.c
PM: base: power: don't try to use non-existing RTC for storing data
* arm64/sve: Use correct size when reinitialising SVE state
arch/arm64/kernel/fpsimd.c
bnx2x: Fix enabling network interfaces without VFs
xen: reset legacy rtc flag for PV domU
dm thin metadata: Fix use-after-free in dm_bm_set_read_only
drm/amdgpu: Fix BUG_ON assert
platform/chrome: cros_ec_proto: Send command again when timeout occurs
* memcg: enable accounting for pids in nested pid namespaces
kernel/pid_namespace.c
* mm/hugetlb: initialize hugetlb_usage in mm_init
include/linux/hugetlb.h
kernel/fork.c
cpufreq: powernv: Fix init_chip_info initialization in numa=off
scsi: qla2xxx: Sync queue idx with queue_pair_map idx
scsi: BusLogic: Fix missing pr_cont() use
* ovl: fix BUG_ON() in may_delete() when called from ovl_cleanup()
fs/overlayfs/dir.c
parisc: fix crash with signals and alloca
net: w5100: check return value after calling platform_get_resource()
* net: fix NULL pointer reference in cipso_v4_doi_free
net/netlabel/netlabel_cipso_v4.c
ath9k: fix sleeping in atomic context
ath9k: fix OOB read ar9300_eeprom_restore_internal
parport: remove non-zero check on count
* ASoC: rockchip: i2s: Fixup config for DAIFMT_DSP_A/B
sound/soc/rockchip/rockchip_i2s.c
* ASoC: rockchip: i2s: Fix regmap_ops hang
sound/soc/rockchip/rockchip_i2s.c
usbip:vhci_hcd USB port can get stuck in the disabled state
usbip: give back URBs for unsent unlink requests during cleanup
usb: musb: musb_dsps: request_irq() after initializing musb
* Revert "USB: xhci: fix U1/U2 handling for hardware with XHCI_INTEL_HOST quirk set"
drivers/usb/host/xhci.c
* cifs: fix wrong release in sess_alloc_buffer() failed path
fs/cifs/sess.c
mmc: core: Return correct emmc response in case of ioctl error
* selftests/bpf: Enlarge select() timeout for test_maps
tools/testing/selftests/bpf/test_maps.c
mmc: rtsx_pci: Fix long reads when clock is prescaled
mmc: sdhci-of-arasan: Check return value of non-void funtions
* of: Don't allow __of_attached_node_sysfs() without CONFIG_SYSFS
drivers/of/kobj.c
* gfs2: Don't call dlm after protocol is unmounted
fs/gfs2/lock_dlm.c
staging: rts5208: Fix get_ms_information() heap buffer size
* rpc: fix gss_svc_init cleanup on failure
net/sunrpc/auth_gss/svcauth_gss.c
* tcp: enable data-less, empty-cookie SYN with TFO_SERVER_COOKIE_NOT_REQD
net/ipv4/tcp_fastopen.c
serial: sh-sci: fix break handling for sysrq
* Bluetooth: Fix handling of LE Enhanced Connection Complete
net/bluetooth/hci_event.c
ARM: tegra: tamonten: Fix UART pad setting
gpu: drm: amd: amdgpu: amdgpu_i2c: fix possible uninitialized-variable access in amdgpu_i2c_router_select_ddc_port()
* Bluetooth: avoid circular locks in sco_sock_connect
net/bluetooth/sco.c
* Bluetooth: schedule SCO timeouts with delayed_work
net/bluetooth/sco.c
net: ethernet: stmmac: Do not use unreachable() in ipq806x_gmac_probe()
arm64: dts: qcom: sdm660: use reg value for memory node
ARM: dts: imx53-ppd: Fix ACHC entry
media: tegra-cec: Handle errors of clk_prepare_enable()
media: TDA1997x: fix tda1997x_query_dv_timings() return value
* media: v4l2-dv-timings.c: fix wrong condition in two for-loops
drivers/media/v4l2-core/v4l2-dv-timings.c
media: imx258: Limit the max analogue gain to 480
media: imx258: Rectify mismatch of VTS value
* ASoC: Intel: bytcr_rt5640: Move "Platform Clock" routes to the maps for the matching in-/output
sound/soc/intel/boards/bytcr_rt5640.c
* bonding: 3ad: fix the concurrency between __bond_release_one() and bond_3ad_state_machine_handler()
drivers/net/bonding/bond_main.c
* Bluetooth: skip invalid hci_sync_conn_complete_evt
net/bluetooth/hci_event.c
ata: sata_dwc_460ex: No need to call phy_exit() befre phy_init()
* samples: bpf: Fix tracex7 error raised on the missing argument
samples/bpf/test_override_return.sh
samples/bpf/tracex7_user.c
staging: ks7010: Fix the initialization of the 'sleep_status' structure
serial: 8250_pci: make setup_port() parameters explicitly unsigned
hvsi: don't panic on tty_register_driver failure
xtensa: ISS: don't panic in rs_init
* serial: 8250: Define RX trigger levels for OxSemi 950 devices
include/uapi/linux/serial_reg.h
s390/jump_label: print real address in a case of a jump label bug
* flow_dissector: Fix out-of-bounds warnings
net/core/flow_dissector.c
* ipv4: ip_output.c: Fix out-of-bounds warning in ip_copy_addrs()
net/ipv4/ip_output.c
video: fbdev: riva: Error out if 'pixclock' equals zero
video: fbdev: kyro: Error out if 'pixclock' equals zero
video: fbdev: asiliantfb: Error out if 'pixclock' equals zero
* bpf/tests: Do not PASS tests without actually testing the result
lib/test_bpf.c
* bpf/tests: Fix copy-and-paste error in double word test
lib/test_bpf.c
drm/amd/amdgpu: Update debugfs link_settings output link_rate field in hex
tty: serial: jsm: hold port lock when reporting modem line changes
staging: board: Fix uninitialized spinlock when attaching genpd
* usb: gadget: composite: Allow bMaxPower=0 if self-powered
drivers/usb/gadget/composite.c
* usb: gadget: u_ether: fix a potential null pointer dereference
drivers/usb/gadget/function/u_ether.c
usb: host: fotg210: fix the actual_length of an iso packet
usb: host: fotg210: fix the endpoint's transactional opportunities calculation
* Smack: Fix wrong semantics in smk_access_entry()
security/smack/smack_access.c
* netlink: Deal with ESRCH error in nlmsg_notify()
net/netlink/af_netlink.c
video: fbdev: kyro: fix a DoS bug by restricting user input
ARM: dts: qcom: apq8064: correct clock names
iio: dac: ad5624r: Fix incorrect handling of an optional regulator.
* tipc: keep the skb in rcv queue until the whole data is read
net/tipc/socket.c
* PCI: Use pci_update_current_state() in pci_enable_device_flags()
drivers/pci/pci.c
crypto: mxs-dcp - Use sg_mapping_iter to copy data
media: dib8000: rewrite the init prbs logic
* userfaultfd: prevent concurrent API initialization
fs/userfaultfd.c
MIPS: Malta: fix alignment of the devicetree buffer
* f2fs: fix to unmap pages from userspace process in punch_hole()
fs/f2fs/file.c
* f2fs: fix to account missing .skipped_gc_rwsem
fs/f2fs/gc.c
* fscache: Fix cookie key hashing
fs/fscache/cookie.c
fs/fscache/internal.h
fs/fscache/main.c
platform/x86: dell-smbios-wmi: Add missing kfree in error-exit from run_smbios_call
scsi: qedi: Fix error codes in qedi_alloc_global_queues()
pinctrl: single: Fix error return code in pcs_parse_bits_in_pinctrl_entry()
openrisc: don't printk() unconditionally
powerpc/stacktrace: Include linux/delay.h
* vfio: Use config not menuconfig for VFIO_NOIOMMU
drivers/vfio/Kconfig
pinctrl: samsung: Fix pinctrl bank pin count
docs: Fix infiniband uverbs minor number
RDMA/iwcm: Release resources if iw_cm module initialization fails
* HID: input: do not report stylus battery state as "full"
drivers/hid/hid-input.c
PCI: aardvark: Fix masking and unmasking legacy INTx interrupts
PCI: aardvark: Increase polling delay to 1.5s while waiting for PIO response
PCI: xilinx-nwl: Enable the clock through CCF
* PCI: Return ~0 data on pciconfig_read() CAP_SYS_ADMIN failure
drivers/pci/syscall.c
* PCI: Restrict ASMedia ASM1062 SATA Max Payload Size Supported
drivers/pci/quirks.c
ARM: 9105/1: atags_to_fdt: don't warn about stack size
libata: add ATA_HORKAGE_NO_NCQ_TRIM for Samsung 860 and 870 SSDs
media: rc-loopback: return number of emitters rather than error
media: uvc: don't do DMA on stack
VMCI: fix NULL pointer dereference when unmapping queue pair
dm crypt: Avoid percpu_counter spinlock contention in crypt_page_alloc()
power: supply: max17042: handle fails of reading status register
block: bfq: fix bfq_set_next_ioprio_data()
* crypto: public_key: fix overflow during implicit conversion
include/crypto/public_key.h
* arm64: head: avoid over-mapping in map_memory
arch/arm64/include/asm/kernel-pgtable.h
arch/arm64/kernel/head.S
soc: aspeed: lpc-ctrl: Fix boundary check for mmap
* tools/thermal/tmon: Add cross compiling support
tools/thermal/tmon/Makefile
* bpf: Fix pointer arithmetic mask tightening under state pruning
include/linux/bpf_verifier.h
kernel/bpf/verifier.c
* bpf: verifier: Allocate idmap scratch in verifier env
include/linux/bpf_verifier.h
kernel/bpf/verifier.c
* bpf: Fix leakage due to insufficient speculative store bypass mitigation
include/linux/bpf_verifier.h
kernel/bpf/verifier.c
* bpf: Introduce BPF nospec instruction for mitigating Spectre v4
arch/arm64/net/bpf_jit_comp.c
include/linux/filter.h
kernel/bpf/core.c
kernel/bpf/disasm.c
* selftests/bpf: fix tests due to const spill/fill
tools/testing/selftests/bpf/test_verifier.c
* bpf: track spill/fill of constants
kernel/bpf/verifier.c
* selftests/bpf: Test variable offset stack access
tools/testing/selftests/bpf/test_verifier.c
* bpf: Sanity check max value for var_off stack access
kernel/bpf/verifier.c
* bpf: Reject indirect var_off stack access in unpriv mode
kernel/bpf/verifier.c
* bpf: Reject indirect var_off stack access in raw mode
kernel/bpf/verifier.c
* bpf: Support variable offset stack access from helpers
kernel/bpf/verifier.c
* bpf: correct slot_type marking logic to allow more stack slot sharing
kernel/bpf/verifier.c
tools/testing/selftests/bpf/test_verifier.c
* bpf/verifier: per-register parent pointers
include/linux/bpf_verifier.h
kernel/bpf/verifier.c
* 9p/xen: Fix end of loop tests for list_for_each_entry
net/9p/trans_xen.c
* include/linux/list.h: add a macro to test if entry is pointing to the head
include/linux/list.h
xen: fix setting of max_pfn in shared_info
powerpc/perf/hv-gpci: Fix counter value parsing
* PCI/MSI: Skip masking MSI-X on Xen PV
drivers/pci/msi.c
blk-zoned: allow BLKREPORTZONE without CAP_SYS_ADMIN
blk-zoned: allow zone management send operations without CAP_SYS_ADMIN
btrfs: reset replace target device to allocation state on close
rtc: tps65910: Correct driver module alias
clk: kirkwood: Fix a clocking boot regression
backlight: pwm_bl: Improve bootloader/kernel device handover
fbmem: don't allow too huge resolutions
* IMA: remove the dependency on CRYPTO_MD5
security/integrity/ima/Kconfig
* IMA: remove -Wmissing-prototypes warning
security/integrity/ima/ima_mok.c
KVM: x86: Update vCPU's hv_clock before back to guest when tsc_offset is adjusted
x86/resctrl: Fix a maybe-uninitialized build warning treated as error
* tty: Fix data race between tiocsti() and flush_to_ldisc()
drivers/tty/tty_io.c
* ubifs: report correct st_size for encrypted symlinks
fs/ubifs/file.c
* f2fs: report correct st_size for encrypted symlinks
fs/f2fs/namei.c
* ext4: report correct st_size for encrypted symlinks
fs/ext4/symlink.c
* fscrypt: add fscrypt_symlink_getattr() for computing st_size
fs/crypto/hooks.c
* netns: protect netns ID lookups with RCU
net/core/net_namespace.c
* ipv4: fix endianness issue in inet_rtm_getroute_build_skb()
net/ipv4/route.c
net: qualcomm: fix QCA7000 checksum handling
* net: sched: Fix qdisc_rate_table refcount leak when get tcf_block failed
net/sched/sch_cbq.c
* ipv4: make exception cache less predictible
net/ipv4/route.c
bcma: Fix memory leak for internally-handled cores
ath6kl: wmi: fix an error code in ath6kl_wmi_sync_point()
tty: serial: fsl_lpuart: fix the wrong mapbase value
usb: bdc: Fix an error handling path in 'bdc_probe()' when no suitable DMA config is available
usb: ehci-orion: Handle errors of clk_prepare_enable() in probe
i2c: mt65xx: fix IRQ check
* CIFS: Fix a potencially linear read overflow
fs/cifs/cifs_unicode.c
mmc: moxart: Fix issue with uninitialized dma_slave_config
mmc: dw_mmc: Fix issue with uninitialized dma_slave_config
i2c: s3c2410: fix IRQ check
i2c: iop3xx: fix deferred probing
* Bluetooth: add timeout sanity check to hci_inquiry
net/bluetooth/hci_core.c
usb: gadget: mv_u3d: request_irq() after initializing UDC
* mac80211: Fix insufficient headroom issue for AMSDU
net/mac80211/tx.c
usb: phy: tahvo: add IRQ check
usb: host: ohci-tmio: add IRQ check
* Bluetooth: Move shutdown callback before flushing tx and rx queue
net/bluetooth/hci_core.c
usb: phy: twl6030: add IRQ checks
usb: phy: fsl-usb: add IRQ check
usb: gadget: udc: at91: add IRQ check
drm/msm/dsi: Fix some reference counted resource leaks
* Bluetooth: fix repeated calls to sco_sock_kill
net/bluetooth/sco.c
arm64: dts: exynos: correct GIC CPU interfaces address range on Exynos7
drm/msm/dpu: make dpu_hw_ctl_clear_all_blendstages clear necessary LMs
* Bluetooth: increase BTNAMSIZ to 21 chars to fix potential buffer overflow
net/bluetooth/cmtp/cmtp.h
soc: qcom: smsm: Fix missed interrupts if state changes while masked
* PCI: PM: Enable PME if it can be signaled from D3cold
drivers/pci/pci.c
* PCI: PM: Avoid forcing PCI_D0 for wakeup reasons inconsistently
drivers/pci/pci.c
media: venus: venc: Fix potential null pointer dereference on pointer fmt
media: em28xx-input: fix refcount bug in em28xx_usb_disconnect
i2c: highlander: add IRQ check
* net: cipso: fix warnings in netlbl_cipsov4_add_std
net/netlabel/netlabel_cipso_v4.c
* tcp: seq_file: Avoid skipping sk during tcp_seek_last_pos
net/ipv4/tcp_ipv4.c
* Bluetooth: sco: prevent information leak in sco_conn_defer_accept()
net/bluetooth/sco.c
media: go7007: remove redundant initialization
media: dvb-usb: fix uninit-value in vp702x_read_mac_addr
media: dvb-usb: fix uninit-value in dvb_usb_adapter_dvb_init
* soc: rockchip: ROCKCHIP_GRF should not default to y, unconditionally
drivers/soc/rockchip/Kconfig
media: TDA1997x: enable EDID support
spi: sprd: Fix the wrong WDG_LOAD_VAL
* certs: Trigger creation of RSA module signing key if it's not an RSA key
certs/Makefile
crypto: qat - use proper type for vf_mask
clocksource/drivers/sh_cmt: Fix wrong setting if don't request IRQ for clock source channel
* lib/mpi: use kcalloc in mpi_resize
lib/mpi/mpiutil.c
spi: spi-pic32: Fix issue with uninitialized dma_slave_config
spi: spi-fsl-dspi: Fix issue with uninitialized dma_slave_config
m68k: emu: Fix invalid free in nfeth_cleanup()
* udf_get_extendedattr() had no boundary checks.
fs/udf/misc.c
* fcntl: fix potential deadlock for &fasync_struct.fa_lock
fs/fcntl.c
crypto: qat - do not export adf_iov_putmsg()
crypto: qat - fix naming for init/shutdown VF to PF notifications
crypto: qat - fix reuse of completion variable
crypto: qat - handle both source of interrupt in VF ISR
crypto: qat - do not ignore errors from enable_vf2pf_comms()
libata: fix ata_host_start()
s390/cio: add dev_busid sysfs entry for each subchannel
* power: supply: max17042_battery: fix typo in MAx17042_TOFF
include/linux/power/max17042_battery.h
nvme-rdma: don't update queue count when failing to set io queues
bcache: add proper error unwinding in bcache_device_init
* isofs: joliet: Fix iocharset=utf8 mount option
fs/isofs/inode.c
fs/isofs/isofs.h
fs/isofs/joliet.c
* udf: Check LVID earlier
fs/udf/super.c
* hrtimer: Avoid double reprogramming in __hrtimer_start_range_ns()
kernel/time/hrtimer.c
* sched/deadline: Fix missing clock update in migrate_task_rq_dl()
kernel/sched/deadline.c
crypto: omap-sham - clear dma flags only after omap_sham_update_dma_stop()
power: supply: axp288_fuel_gauge: Report register-address on readb / writeb errors
* sched/deadline: Fix reset_on_fork reporting of DL tasks
kernel/sched/deadline.c
kernel/sched/sched.h
crypto: mxs-dcp - Check for DMA mapping errors
* regmap: fix the offset of register error log
drivers/base/regmap/regmap.c
* locking/mutex: Fix HANDOFF condition
kernel/locking/mutex.c
* PCI: Call Max Payload Size-related fixup quirks early
drivers/pci/quirks.c
x86/reboot: Limit Dell Optiplex 990 quirk to early BIOS versions
usb: mtu3: fix the wrong HS mult value
usb: mtu3: use @mult for HS isoc or intr
usb: host: xhci-rcar: Don't reload firmware after the completion
* ALSA: usb-audio: Add registration quirk for JBL Quantum 800
sound/usb/quirks.c
Revert "btrfs: compression: don't try to compress if we don't have enough pages"
* mm/page_alloc: speed up the iteration of max_order
mm/page_alloc.c
net: ll_temac: Remove left-over debug message
powerpc/boot: Delete unneeded .globl _zimage_start
powerpc/module64: Fix comment in R_PPC64_ENTRY handling
crypto: talitos - reduce max key size for SEC1
* SUNRPC/nfs: Fix return value for nfs4_callback_compound()
fs/nfs/callback_xdr.c
include/linux/sunrpc/svc.h
net/sunrpc/svc.c
* ipv4/icmp: l3mdev: Perform icmp error route lookup on source device routing table (v2)
net/ipv4/icmp.c
USB: serial: mos7720: improve OOM-handling in read_mos_reg()
* igmp: Add ip_mc_list lock in ip_check_mc_rcu
net/ipv4/igmp.c
ARM: imx: fix missing 3rd argument in macro imx_mmdc_perf_init
ARM: imx: add missing clk_disable_unprepare()
media: stkwebcam: fix memory leak in stk_camera_probe
* clk: fix build warning for orphan_list
drivers/clk/clk.c
* ALSA: pcm: fix divide error in snd_pcm_lib_ioctl
sound/core/pcm_lib.c
ARM: 8918/2: only build return_address() if needed
* cryptoloop: add a deprecation warning
drivers/block/Kconfig
perf/x86/amd/ibs: Work around erratum #1197
perf/x86/intel/pt: Fix mask of num_address_ranges
qede: Fix memset corruption
net: macb: Add a NULL check on desc_ptp
qed: Fix the VF msix vectors flow
gpu: ipu-v3: Fix i.MX IPU-v3 offset calculations for (semi)planar U/V formats
xtensa: fix kconfig unmet dependency warning for HAVE_FUTEX_CMPXCHG
* ext4: fix race writing to an inline_data file while its xattrs are changing
fs/ext4/inline.c
Merge 4.19.206 into android-4.19-stable
Linux 4.19.206
* net: don't unconditionally copy_from_user a struct ifreq for socket ioctls
include/linux/netdevice.h
net/socket.c
Revert "floppy: reintroduce O_NDELAY fix"
KVM: x86/mmu: Treat NX as used (not reserved) for all !TDP shadow MMUs
fbmem: add margin check to fb_check_caps()
vt_kdsetmode: extend console locking
* net/rds: dma_map_sg is entitled to merge entries
net/rds/ib_frmr.c
drm/nouveau/disp: power down unused DP links during init
* drm: Copy drm_wait_vblank to user before returning
drivers/gpu/drm/drm_ioc32.c
qed: Fix null-pointer dereference in qed_rdma_create_qp()
qed: qed ll2 race condition fixes
vringh: Use wiov->used to check for read/write desc order
virtio_pci: Support surprise removal of virtio pci device
virtio: Improve vq->broken access to avoid any compiler optimization
* opp: remove WARN when no valid OPPs remain
drivers/opp/of.c
usb: gadget: u_audio: fix race condition on endpoint stop
net: hns3: fix get wrong pfc_en when query PFC configuration
net: marvell: fix MVNETA_TX_IN_PRGRS bit number
xgene-v2: Fix a resource leak in the error handling path of 'xge_probe()'
* ip_gre: add validation for csum_start
net/ipv4/ip_gre.c
e1000e: Fix the max snoop/no-snoop latency for 10M
IB/hfi1: Fix possible null-pointer dereference in _extend_sdma_tx_descs()
* usb: dwc3: gadget: Stop EP0 transfers during pullup disable
drivers/usb/dwc3/gadget.c
* usb: dwc3: gadget: Fix dwc3_calc_trbs_left()
drivers/usb/dwc3/gadget.c
USB: serial: option: add new VID/PID to support Fibocom FG150
Revert "USB: serial: ch341: fix character loss at high transfer rates"
can: usb: esd_usb2: esd_usb2_rx_event(): fix the interchange of the CAN RX and TX error counters
* once: Fix panic when module unload
include/linux/once.h
lib/once.c
* netfilter: conntrack: collect all entries in one cycle
net/netfilter/nf_conntrack_core.c
ARC: Fix CONFIG_STACKDEPOT
* bpf: Fix truncation handling for mod32 dst reg wrt zero
kernel/bpf/verifier.c
* bpf: Fix 32 bit src register truncation on div/mod
include/linux/filter.h
kernel/bpf/verifier.c
* bpf: Do not use ax register in interpreter on div/mod
kernel/bpf/core.c
* net: qrtr: fix another OOB Read in qrtr_endpoint_post
net/qrtr/qrtr.c
Bug: 201722185
Change-Id: Ib431fe52b39577bd49f14f4b28c49b54fa247c54
Signed-off-by: Wilson Sung <wilsonsung@google.com>
2019 lines
52 KiB
C
2019 lines
52 KiB
C
/*
|
|
* linux/mm/memory_hotplug.c
|
|
*
|
|
* Copyright (C)
|
|
*/
|
|
|
|
#include <linux/stddef.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/swap.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/export.h>
|
|
#include <linux/pagevec.h>
|
|
#include <linux/writeback.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sysctl.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/memory.h>
|
|
#include <linux/memremap.h>
|
|
#include <linux/memory_hotplug.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/migrate.h>
|
|
#include <linux/page-isolation.h>
|
|
#include <linux/pfn.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/mm_inline.h>
|
|
#include <linux/firmware-map.h>
|
|
#include <linux/stop_machine.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/bootmem.h>
|
|
#include <linux/compaction.h>
|
|
#include <linux/device.h>
|
|
#include <linux/rmap.h>
|
|
|
|
#include <asm/tlbflush.h>
|
|
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* online_page_callback contains pointer to current page onlining function.
|
|
* Initially it is generic_online_page(). If it is required it could be
|
|
* changed by calling set_online_page_callback() for callback registration
|
|
* and restore_online_page_callback() for generic callback restore.
|
|
*/
|
|
|
|
static int generic_online_page(struct page *page);
|
|
|
|
static online_page_callback_t online_page_callback = generic_online_page;
|
|
static DEFINE_MUTEX(online_page_callback_lock);
|
|
|
|
DEFINE_STATIC_PERCPU_RWSEM(mem_hotplug_lock);
|
|
|
|
void get_online_mems(void)
|
|
{
|
|
percpu_down_read(&mem_hotplug_lock);
|
|
}
|
|
|
|
void put_online_mems(void)
|
|
{
|
|
percpu_up_read(&mem_hotplug_lock);
|
|
}
|
|
|
|
#ifndef CONFIG_MEMORY_HOTPLUG_MOVABLE_NODE
|
|
bool movable_node_enabled = false;
|
|
#else
|
|
bool movable_node_enabled = true;
|
|
#endif
|
|
|
|
#ifndef CONFIG_MEMORY_HOTPLUG_DEFAULT_ONLINE
|
|
bool memhp_auto_online;
|
|
#else
|
|
bool memhp_auto_online = true;
|
|
#endif
|
|
EXPORT_SYMBOL_GPL(memhp_auto_online);
|
|
|
|
static int __init setup_memhp_default_state(char *str)
|
|
{
|
|
if (!strcmp(str, "online"))
|
|
memhp_auto_online = true;
|
|
else if (!strcmp(str, "offline"))
|
|
memhp_auto_online = false;
|
|
|
|
return 1;
|
|
}
|
|
__setup("memhp_default_state=", setup_memhp_default_state);
|
|
|
|
void mem_hotplug_begin(void)
|
|
{
|
|
cpus_read_lock();
|
|
percpu_down_write(&mem_hotplug_lock);
|
|
}
|
|
|
|
void mem_hotplug_done(void)
|
|
{
|
|
percpu_up_write(&mem_hotplug_lock);
|
|
cpus_read_unlock();
|
|
}
|
|
|
|
/* add this memory to iomem resource */
|
|
static struct resource *register_memory_resource(u64 start, u64 size)
|
|
{
|
|
struct resource *res, *conflict;
|
|
res = kzalloc(sizeof(struct resource), GFP_KERNEL);
|
|
if (!res)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
res->name = "System RAM";
|
|
res->start = start;
|
|
res->end = start + size - 1;
|
|
res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
|
|
conflict = request_resource_conflict(&iomem_resource, res);
|
|
if (conflict) {
|
|
if (conflict->desc == IORES_DESC_DEVICE_PRIVATE_MEMORY) {
|
|
pr_debug("Device unaddressable memory block "
|
|
"memory hotplug at %#010llx !\n",
|
|
(unsigned long long)start);
|
|
}
|
|
pr_debug("System RAM resource %pR cannot be added\n", res);
|
|
kfree(res);
|
|
return ERR_PTR(-EEXIST);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void release_memory_resource(struct resource *res)
|
|
{
|
|
if (!res)
|
|
return;
|
|
release_resource(res);
|
|
kfree(res);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_MEMORY_HOTPLUG_SPARSE
|
|
void get_page_bootmem(unsigned long info, struct page *page,
|
|
unsigned long type)
|
|
{
|
|
page->freelist = (void *)type;
|
|
SetPagePrivate(page);
|
|
set_page_private(page, info);
|
|
page_ref_inc(page);
|
|
}
|
|
|
|
void put_page_bootmem(struct page *page)
|
|
{
|
|
unsigned long type;
|
|
|
|
type = (unsigned long) page->freelist;
|
|
BUG_ON(type < MEMORY_HOTPLUG_MIN_BOOTMEM_TYPE ||
|
|
type > MEMORY_HOTPLUG_MAX_BOOTMEM_TYPE);
|
|
|
|
if (page_ref_dec_return(page) == 1) {
|
|
page->freelist = NULL;
|
|
ClearPagePrivate(page);
|
|
set_page_private(page, 0);
|
|
INIT_LIST_HEAD(&page->lru);
|
|
free_reserved_page(page);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_HAVE_BOOTMEM_INFO_NODE
|
|
#ifndef CONFIG_SPARSEMEM_VMEMMAP
|
|
static void register_page_bootmem_info_section(unsigned long start_pfn)
|
|
{
|
|
unsigned long *usemap, mapsize, section_nr, i;
|
|
struct mem_section *ms;
|
|
struct page *page, *memmap;
|
|
|
|
section_nr = pfn_to_section_nr(start_pfn);
|
|
ms = __nr_to_section(section_nr);
|
|
|
|
/* Get section's memmap address */
|
|
memmap = sparse_decode_mem_map(ms->section_mem_map, section_nr);
|
|
|
|
/*
|
|
* Get page for the memmap's phys address
|
|
* XXX: need more consideration for sparse_vmemmap...
|
|
*/
|
|
page = virt_to_page(memmap);
|
|
mapsize = sizeof(struct page) * PAGES_PER_SECTION;
|
|
mapsize = PAGE_ALIGN(mapsize) >> PAGE_SHIFT;
|
|
|
|
/* remember memmap's page */
|
|
for (i = 0; i < mapsize; i++, page++)
|
|
get_page_bootmem(section_nr, page, SECTION_INFO);
|
|
|
|
usemap = ms->pageblock_flags;
|
|
page = virt_to_page(usemap);
|
|
|
|
mapsize = PAGE_ALIGN(usemap_size()) >> PAGE_SHIFT;
|
|
|
|
for (i = 0; i < mapsize; i++, page++)
|
|
get_page_bootmem(section_nr, page, MIX_SECTION_INFO);
|
|
|
|
}
|
|
#else /* CONFIG_SPARSEMEM_VMEMMAP */
|
|
static void register_page_bootmem_info_section(unsigned long start_pfn)
|
|
{
|
|
unsigned long *usemap, mapsize, section_nr, i;
|
|
struct mem_section *ms;
|
|
struct page *page, *memmap;
|
|
|
|
section_nr = pfn_to_section_nr(start_pfn);
|
|
ms = __nr_to_section(section_nr);
|
|
|
|
memmap = sparse_decode_mem_map(ms->section_mem_map, section_nr);
|
|
|
|
register_page_bootmem_memmap(section_nr, memmap, PAGES_PER_SECTION);
|
|
|
|
usemap = ms->pageblock_flags;
|
|
page = virt_to_page(usemap);
|
|
|
|
mapsize = PAGE_ALIGN(usemap_size()) >> PAGE_SHIFT;
|
|
|
|
for (i = 0; i < mapsize; i++, page++)
|
|
get_page_bootmem(section_nr, page, MIX_SECTION_INFO);
|
|
}
|
|
#endif /* !CONFIG_SPARSEMEM_VMEMMAP */
|
|
|
|
void __init register_page_bootmem_info_node(struct pglist_data *pgdat)
|
|
{
|
|
unsigned long i, pfn, end_pfn, nr_pages;
|
|
int node = pgdat->node_id;
|
|
struct page *page;
|
|
|
|
nr_pages = PAGE_ALIGN(sizeof(struct pglist_data)) >> PAGE_SHIFT;
|
|
page = virt_to_page(pgdat);
|
|
|
|
for (i = 0; i < nr_pages; i++, page++)
|
|
get_page_bootmem(node, page, NODE_INFO);
|
|
|
|
pfn = pgdat->node_start_pfn;
|
|
end_pfn = pgdat_end_pfn(pgdat);
|
|
|
|
/* register section info */
|
|
for (; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
|
|
/*
|
|
* Some platforms can assign the same pfn to multiple nodes - on
|
|
* node0 as well as nodeN. To avoid registering a pfn against
|
|
* multiple nodes we check that this pfn does not already
|
|
* reside in some other nodes.
|
|
*/
|
|
if (pfn_valid(pfn) && (early_pfn_to_nid(pfn) == node))
|
|
register_page_bootmem_info_section(pfn);
|
|
}
|
|
}
|
|
#endif /* CONFIG_HAVE_BOOTMEM_INFO_NODE */
|
|
|
|
static int __meminit __add_section(int nid, unsigned long phys_start_pfn,
|
|
struct vmem_altmap *altmap, bool want_memblock)
|
|
{
|
|
int ret;
|
|
|
|
if (pfn_valid(phys_start_pfn))
|
|
return -EEXIST;
|
|
|
|
ret = sparse_add_one_section(nid, phys_start_pfn, altmap);
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
/*
|
|
* Reasonably generic function for adding memory. It is
|
|
* expected that archs that support memory hotplug will
|
|
* call this function after deciding the zone to which to
|
|
* add the new pages.
|
|
*/
|
|
int __ref __add_pages(int nid, unsigned long phys_start_pfn,
|
|
unsigned long nr_pages, struct vmem_altmap *altmap,
|
|
bool want_memblock)
|
|
{
|
|
unsigned long i;
|
|
int err = 0;
|
|
int start_sec, end_sec;
|
|
|
|
/* during initialize mem_map, align hot-added range to section */
|
|
start_sec = pfn_to_section_nr(phys_start_pfn);
|
|
end_sec = pfn_to_section_nr(phys_start_pfn + nr_pages - 1);
|
|
|
|
if (altmap) {
|
|
/*
|
|
* Validate altmap is within bounds of the total request
|
|
*/
|
|
if (altmap->base_pfn != phys_start_pfn
|
|
|| vmem_altmap_offset(altmap) > nr_pages) {
|
|
pr_warn_once("memory add fail, invalid altmap\n");
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
altmap->alloc = 0;
|
|
}
|
|
|
|
for (i = start_sec; i <= end_sec; i++) {
|
|
err = __add_section(nid, section_nr_to_pfn(i), altmap,
|
|
want_memblock);
|
|
|
|
/*
|
|
* EEXIST is finally dealt with by ioresource collision
|
|
* check. see add_memory() => register_memory_resource()
|
|
* Warning will be printed if there is collision.
|
|
*/
|
|
if (err && (err != -EEXIST))
|
|
break;
|
|
err = 0;
|
|
cond_resched();
|
|
}
|
|
vmemmap_populate_print_last();
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/* find the smallest valid pfn in the range [start_pfn, end_pfn) */
|
|
static unsigned long find_smallest_section_pfn(int nid, struct zone *zone,
|
|
unsigned long start_pfn,
|
|
unsigned long end_pfn)
|
|
{
|
|
for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SECTION) {
|
|
if (unlikely(!pfn_to_online_page(start_pfn)))
|
|
continue;
|
|
|
|
if (unlikely(pfn_to_nid(start_pfn) != nid))
|
|
continue;
|
|
|
|
if (zone && zone != page_zone(pfn_to_page(start_pfn)))
|
|
continue;
|
|
|
|
return start_pfn;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* find the biggest valid pfn in the range [start_pfn, end_pfn). */
|
|
static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,
|
|
unsigned long start_pfn,
|
|
unsigned long end_pfn)
|
|
{
|
|
unsigned long pfn;
|
|
|
|
/* pfn is the end pfn of a memory section. */
|
|
pfn = end_pfn - 1;
|
|
for (; pfn >= start_pfn; pfn -= PAGES_PER_SECTION) {
|
|
if (unlikely(!pfn_to_online_page(pfn)))
|
|
continue;
|
|
|
|
if (unlikely(pfn_to_nid(pfn) != nid))
|
|
continue;
|
|
|
|
if (zone && zone != page_zone(pfn_to_page(pfn)))
|
|
continue;
|
|
|
|
return pfn;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
|
|
unsigned long end_pfn)
|
|
{
|
|
unsigned long zone_start_pfn = zone->zone_start_pfn;
|
|
unsigned long z = zone_end_pfn(zone); /* zone_end_pfn namespace clash */
|
|
unsigned long zone_end_pfn = z;
|
|
unsigned long pfn;
|
|
int nid = zone_to_nid(zone);
|
|
|
|
zone_span_writelock(zone);
|
|
if (zone_start_pfn == start_pfn) {
|
|
/*
|
|
* If the section is smallest section in the zone, it need
|
|
* shrink zone->zone_start_pfn and zone->zone_spanned_pages.
|
|
* In this case, we find second smallest valid mem_section
|
|
* for shrinking zone.
|
|
*/
|
|
pfn = find_smallest_section_pfn(nid, zone, end_pfn,
|
|
zone_end_pfn);
|
|
if (pfn) {
|
|
zone->zone_start_pfn = pfn;
|
|
zone->spanned_pages = zone_end_pfn - pfn;
|
|
}
|
|
} else if (zone_end_pfn == end_pfn) {
|
|
/*
|
|
* If the section is biggest section in the zone, it need
|
|
* shrink zone->spanned_pages.
|
|
* In this case, we find second biggest valid mem_section for
|
|
* shrinking zone.
|
|
*/
|
|
pfn = find_biggest_section_pfn(nid, zone, zone_start_pfn,
|
|
start_pfn);
|
|
if (pfn)
|
|
zone->spanned_pages = pfn - zone_start_pfn + 1;
|
|
}
|
|
|
|
/*
|
|
* The section is not biggest or smallest mem_section in the zone, it
|
|
* only creates a hole in the zone. So in this case, we need not
|
|
* change the zone. But perhaps, the zone has only hole data. Thus
|
|
* it check the zone has only hole or not.
|
|
*/
|
|
pfn = zone_start_pfn;
|
|
for (; pfn < zone_end_pfn; pfn += PAGES_PER_SECTION) {
|
|
if (unlikely(!pfn_to_online_page(pfn)))
|
|
continue;
|
|
|
|
if (page_zone(pfn_to_page(pfn)) != zone)
|
|
continue;
|
|
|
|
/* If the section is current section, it continues the loop */
|
|
if (start_pfn == pfn)
|
|
continue;
|
|
|
|
/* If we find valid section, we have nothing to do */
|
|
zone_span_writeunlock(zone);
|
|
return;
|
|
}
|
|
|
|
/* The zone has no valid section */
|
|
zone->zone_start_pfn = 0;
|
|
zone->spanned_pages = 0;
|
|
zone_span_writeunlock(zone);
|
|
}
|
|
|
|
static void update_pgdat_span(struct pglist_data *pgdat)
|
|
{
|
|
unsigned long node_start_pfn = 0, node_end_pfn = 0;
|
|
struct zone *zone;
|
|
|
|
for (zone = pgdat->node_zones;
|
|
zone < pgdat->node_zones + MAX_NR_ZONES; zone++) {
|
|
unsigned long zone_end_pfn = zone->zone_start_pfn +
|
|
zone->spanned_pages;
|
|
|
|
/* No need to lock the zones, they can't change. */
|
|
if (!zone->spanned_pages)
|
|
continue;
|
|
if (!node_end_pfn) {
|
|
node_start_pfn = zone->zone_start_pfn;
|
|
node_end_pfn = zone_end_pfn;
|
|
continue;
|
|
}
|
|
|
|
if (zone_end_pfn > node_end_pfn)
|
|
node_end_pfn = zone_end_pfn;
|
|
if (zone->zone_start_pfn < node_start_pfn)
|
|
node_start_pfn = zone->zone_start_pfn;
|
|
}
|
|
|
|
pgdat->node_start_pfn = node_start_pfn;
|
|
pgdat->node_spanned_pages = node_end_pfn - node_start_pfn;
|
|
}
|
|
|
|
void __ref remove_pfn_range_from_zone(struct zone *zone,
|
|
unsigned long start_pfn,
|
|
unsigned long nr_pages)
|
|
{
|
|
struct pglist_data *pgdat = zone->zone_pgdat;
|
|
unsigned long flags;
|
|
|
|
#ifdef CONFIG_ZONE_DEVICE
|
|
/*
|
|
* Zone shrinking code cannot properly deal with ZONE_DEVICE. So
|
|
* we will not try to shrink the zones - which is okay as
|
|
* set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
|
|
*/
|
|
if (zone_idx(zone) == ZONE_DEVICE)
|
|
return;
|
|
#endif
|
|
|
|
clear_zone_contiguous(zone);
|
|
|
|
pgdat_resize_lock(zone->zone_pgdat, &flags);
|
|
shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
|
|
update_pgdat_span(pgdat);
|
|
pgdat_resize_unlock(zone->zone_pgdat, &flags);
|
|
|
|
set_zone_contiguous(zone);
|
|
}
|
|
|
|
static void __remove_section(struct mem_section *ms, unsigned long map_offset,
|
|
struct vmem_altmap *altmap)
|
|
{
|
|
unsigned long start_pfn;
|
|
int scn_nr;
|
|
|
|
if (WARN_ON_ONCE(!valid_section(ms)))
|
|
return;
|
|
|
|
scn_nr = __section_nr(ms);
|
|
start_pfn = section_nr_to_pfn((unsigned long)scn_nr);
|
|
|
|
sparse_remove_one_section(ms, map_offset, altmap);
|
|
}
|
|
|
|
/**
|
|
* __remove_pages() - remove sections of pages
|
|
* @phys_start_pfn: starting pageframe (must be aligned to start of a section)
|
|
* @nr_pages: number of pages to remove (must be multiple of section size)
|
|
* @altmap: alternative device page map or %NULL if default memmap is used
|
|
*
|
|
* Generic helper function to remove section mappings and sysfs entries
|
|
* for the section of the memory we are removing. Caller needs to make
|
|
* sure that pages are marked reserved and zones are adjust properly by
|
|
* calling offline_pages().
|
|
*/
|
|
void __remove_pages(unsigned long phys_start_pfn, unsigned long nr_pages,
|
|
struct vmem_altmap *altmap)
|
|
{
|
|
unsigned long i;
|
|
unsigned long map_offset = 0;
|
|
int sections_to_remove;
|
|
|
|
if (altmap)
|
|
map_offset = vmem_altmap_offset(altmap);
|
|
|
|
/*
|
|
* We can only remove entire sections
|
|
*/
|
|
BUG_ON(phys_start_pfn & ~PAGE_SECTION_MASK);
|
|
BUG_ON(nr_pages % PAGES_PER_SECTION);
|
|
|
|
sections_to_remove = nr_pages / PAGES_PER_SECTION;
|
|
for (i = 0; i < sections_to_remove; i++) {
|
|
unsigned long pfn = phys_start_pfn + i*PAGES_PER_SECTION;
|
|
|
|
cond_resched();
|
|
__remove_section(__pfn_to_section(pfn), map_offset, altmap);
|
|
map_offset = 0;
|
|
}
|
|
}
|
|
|
|
int set_online_page_callback(online_page_callback_t callback)
|
|
{
|
|
int rc = -EINVAL;
|
|
|
|
get_online_mems();
|
|
mutex_lock(&online_page_callback_lock);
|
|
|
|
if (online_page_callback == generic_online_page) {
|
|
online_page_callback = callback;
|
|
rc = 0;
|
|
}
|
|
|
|
mutex_unlock(&online_page_callback_lock);
|
|
put_online_mems();
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(set_online_page_callback);
|
|
|
|
int restore_online_page_callback(online_page_callback_t callback)
|
|
{
|
|
int rc = -EINVAL;
|
|
|
|
get_online_mems();
|
|
mutex_lock(&online_page_callback_lock);
|
|
|
|
if (online_page_callback == callback) {
|
|
online_page_callback = generic_online_page;
|
|
rc = 0;
|
|
}
|
|
|
|
mutex_unlock(&online_page_callback_lock);
|
|
put_online_mems();
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(restore_online_page_callback);
|
|
|
|
void __online_page_set_limits(struct page *page)
|
|
{
|
|
}
|
|
EXPORT_SYMBOL_GPL(__online_page_set_limits);
|
|
|
|
void __online_page_increment_counters(struct page *page)
|
|
{
|
|
adjust_managed_page_count(page, 1);
|
|
}
|
|
EXPORT_SYMBOL_GPL(__online_page_increment_counters);
|
|
|
|
void __online_page_free(struct page *page)
|
|
{
|
|
__free_reserved_page(page);
|
|
}
|
|
EXPORT_SYMBOL_GPL(__online_page_free);
|
|
|
|
static int generic_online_page(struct page *page)
|
|
{
|
|
__online_page_set_limits(page);
|
|
__online_page_increment_counters(page);
|
|
__online_page_free(page);
|
|
return 0;
|
|
}
|
|
|
|
static void __free_pages_hotplug(struct page *page, unsigned int order)
|
|
{
|
|
unsigned int nr_pages = 1 << order;
|
|
struct page *p = page;
|
|
unsigned int loop;
|
|
|
|
adjust_managed_page_count(page, nr_pages);
|
|
for (loop = 0; loop < nr_pages; loop++, p++) {
|
|
__online_page_set_limits(p);
|
|
ClearPageReserved(p);
|
|
set_page_count(p, 0);
|
|
}
|
|
|
|
set_page_refcounted(page);
|
|
__free_pages(page, order);
|
|
}
|
|
|
|
static void __free_pages_memory(unsigned long start,
|
|
unsigned long nr_pages, void *arg)
|
|
{
|
|
unsigned long order;
|
|
unsigned long onlined_pages = *(unsigned long *)arg;
|
|
struct page *page;
|
|
unsigned long i;
|
|
unsigned long phy_addr;
|
|
|
|
for (i = 0; i < nr_pages; i += (1UL << order)) {
|
|
order = min(MAX_ORDER - 1UL, __ffs(start + i));
|
|
page = pfn_to_page(start + i);
|
|
phy_addr = page_to_phys(page);
|
|
|
|
if (phy_addr >= bootloader_memory_limit)
|
|
break;
|
|
|
|
while ((1UL << order) > nr_pages - i ||
|
|
phy_addr + ((1UL << order) * PAGE_SIZE)
|
|
> bootloader_memory_limit)
|
|
order--;
|
|
|
|
__free_pages_hotplug(page, order);
|
|
onlined_pages += (1UL << order);
|
|
}
|
|
|
|
*(unsigned long *)arg = onlined_pages;
|
|
}
|
|
|
|
static int online_pages_range(unsigned long start_pfn, unsigned long nr_pages,
|
|
void *arg)
|
|
{
|
|
if (PageReserved(pfn_to_page(start_pfn)))
|
|
__free_pages_memory(start_pfn, nr_pages, arg);
|
|
|
|
online_mem_sections(start_pfn, start_pfn + nr_pages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* check which state of node_states will be changed when online memory */
|
|
static void node_states_check_changes_online(unsigned long nr_pages,
|
|
struct zone *zone, struct memory_notify *arg)
|
|
{
|
|
int nid = zone_to_nid(zone);
|
|
enum zone_type zone_last = ZONE_NORMAL;
|
|
|
|
/*
|
|
* If we have HIGHMEM or movable node, node_states[N_NORMAL_MEMORY]
|
|
* contains nodes which have zones of 0...ZONE_NORMAL,
|
|
* set zone_last to ZONE_NORMAL.
|
|
*
|
|
* If we don't have HIGHMEM nor movable node,
|
|
* node_states[N_NORMAL_MEMORY] contains nodes which have zones of
|
|
* 0...ZONE_MOVABLE, set zone_last to ZONE_MOVABLE.
|
|
*/
|
|
if (N_MEMORY == N_NORMAL_MEMORY)
|
|
zone_last = ZONE_MOVABLE;
|
|
|
|
/*
|
|
* if the memory to be online is in a zone of 0...zone_last, and
|
|
* the zones of 0...zone_last don't have memory before online, we will
|
|
* need to set the node to node_states[N_NORMAL_MEMORY] after
|
|
* the memory is online.
|
|
*/
|
|
if (zone_idx(zone) <= zone_last && !node_state(nid, N_NORMAL_MEMORY))
|
|
arg->status_change_nid_normal = nid;
|
|
else
|
|
arg->status_change_nid_normal = -1;
|
|
|
|
#ifdef CONFIG_HIGHMEM
|
|
/*
|
|
* If we have movable node, node_states[N_HIGH_MEMORY]
|
|
* contains nodes which have zones of 0...ZONE_HIGHMEM,
|
|
* set zone_last to ZONE_HIGHMEM.
|
|
*
|
|
* If we don't have movable node, node_states[N_NORMAL_MEMORY]
|
|
* contains nodes which have zones of 0...ZONE_MOVABLE,
|
|
* set zone_last to ZONE_MOVABLE.
|
|
*/
|
|
zone_last = ZONE_HIGHMEM;
|
|
if (N_MEMORY == N_HIGH_MEMORY)
|
|
zone_last = ZONE_MOVABLE;
|
|
|
|
if (zone_idx(zone) <= zone_last && !node_state(nid, N_HIGH_MEMORY))
|
|
arg->status_change_nid_high = nid;
|
|
else
|
|
arg->status_change_nid_high = -1;
|
|
#else
|
|
arg->status_change_nid_high = arg->status_change_nid_normal;
|
|
#endif
|
|
|
|
/*
|
|
* if the node don't have memory befor online, we will need to
|
|
* set the node to node_states[N_MEMORY] after the memory
|
|
* is online.
|
|
*/
|
|
if (!node_state(nid, N_MEMORY))
|
|
arg->status_change_nid = nid;
|
|
else
|
|
arg->status_change_nid = -1;
|
|
}
|
|
|
|
static void node_states_set_node(int node, struct memory_notify *arg)
|
|
{
|
|
if (arg->status_change_nid_normal >= 0)
|
|
node_set_state(node, N_NORMAL_MEMORY);
|
|
|
|
if (arg->status_change_nid_high >= 0)
|
|
node_set_state(node, N_HIGH_MEMORY);
|
|
|
|
node_set_state(node, N_MEMORY);
|
|
}
|
|
|
|
static void __meminit resize_zone_range(struct zone *zone, unsigned long start_pfn,
|
|
unsigned long nr_pages)
|
|
{
|
|
unsigned long old_end_pfn = zone_end_pfn(zone);
|
|
|
|
if (zone_is_empty(zone) || start_pfn < zone->zone_start_pfn)
|
|
zone->zone_start_pfn = start_pfn;
|
|
|
|
zone->spanned_pages = max(start_pfn + nr_pages, old_end_pfn) - zone->zone_start_pfn;
|
|
}
|
|
|
|
static void __meminit resize_pgdat_range(struct pglist_data *pgdat, unsigned long start_pfn,
|
|
unsigned long nr_pages)
|
|
{
|
|
unsigned long old_end_pfn = pgdat_end_pfn(pgdat);
|
|
|
|
if (!pgdat->node_spanned_pages || start_pfn < pgdat->node_start_pfn)
|
|
pgdat->node_start_pfn = start_pfn;
|
|
|
|
pgdat->node_spanned_pages = max(start_pfn + nr_pages, old_end_pfn) - pgdat->node_start_pfn;
|
|
}
|
|
|
|
void __ref move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
|
|
unsigned long nr_pages, struct vmem_altmap *altmap)
|
|
{
|
|
struct pglist_data *pgdat = zone->zone_pgdat;
|
|
int nid = pgdat->node_id;
|
|
unsigned long flags;
|
|
|
|
if (zone_is_empty(zone))
|
|
init_currently_empty_zone(zone, start_pfn, nr_pages);
|
|
|
|
clear_zone_contiguous(zone);
|
|
|
|
/* TODO Huh pgdat is irqsave while zone is not. It used to be like that before */
|
|
pgdat_resize_lock(pgdat, &flags);
|
|
zone_span_writelock(zone);
|
|
resize_zone_range(zone, start_pfn, nr_pages);
|
|
zone_span_writeunlock(zone);
|
|
resize_pgdat_range(pgdat, start_pfn, nr_pages);
|
|
pgdat_resize_unlock(pgdat, &flags);
|
|
|
|
/*
|
|
* TODO now we have a visible range of pages which are not associated
|
|
* with their zone properly. Not nice but set_pfnblock_flags_mask
|
|
* expects the zone spans the pfn range. All the pages in the range
|
|
* are reserved so nobody should be touching them so we should be safe
|
|
*/
|
|
memmap_init_zone(nr_pages, nid, zone_idx(zone), start_pfn,
|
|
MEMINIT_HOTPLUG, altmap);
|
|
|
|
set_zone_contiguous(zone);
|
|
}
|
|
|
|
/*
|
|
* Returns a default kernel memory zone for the given pfn range.
|
|
* If no kernel zone covers this pfn range it will automatically go
|
|
* to the ZONE_NORMAL.
|
|
*/
|
|
static struct zone *default_kernel_zone_for_pfn(int nid, unsigned long start_pfn,
|
|
unsigned long nr_pages)
|
|
{
|
|
struct pglist_data *pgdat = NODE_DATA(nid);
|
|
int zid;
|
|
|
|
for (zid = 0; zid <= ZONE_NORMAL; zid++) {
|
|
struct zone *zone = &pgdat->node_zones[zid];
|
|
|
|
if (zone_intersects(zone, start_pfn, nr_pages))
|
|
return zone;
|
|
}
|
|
|
|
return &pgdat->node_zones[ZONE_NORMAL];
|
|
}
|
|
|
|
static inline struct zone *default_zone_for_pfn(int nid, unsigned long start_pfn,
|
|
unsigned long nr_pages)
|
|
{
|
|
struct zone *kernel_zone = default_kernel_zone_for_pfn(nid, start_pfn,
|
|
nr_pages);
|
|
struct zone *movable_zone = &NODE_DATA(nid)->node_zones[ZONE_MOVABLE];
|
|
bool in_kernel = zone_intersects(kernel_zone, start_pfn, nr_pages);
|
|
bool in_movable = zone_intersects(movable_zone, start_pfn, nr_pages);
|
|
|
|
/*
|
|
* We inherit the existing zone in a simple case where zones do not
|
|
* overlap in the given range
|
|
*/
|
|
if (in_kernel ^ in_movable)
|
|
return (in_kernel) ? kernel_zone : movable_zone;
|
|
|
|
/*
|
|
* If the range doesn't belong to any zone or two zones overlap in the
|
|
* given range then we use movable zone only if movable_node is
|
|
* enabled because we always online to a kernel zone by default.
|
|
*/
|
|
return movable_node_enabled ? movable_zone : kernel_zone;
|
|
}
|
|
|
|
struct zone *zone_for_pfn_range(int online_type, int nid,
|
|
unsigned long start_pfn, unsigned long nr_pages)
|
|
{
|
|
if (online_type == MMOP_ONLINE_KERNEL)
|
|
return default_kernel_zone_for_pfn(nid, start_pfn, nr_pages);
|
|
|
|
if (online_type == MMOP_ONLINE_MOVABLE)
|
|
return &NODE_DATA(nid)->node_zones[ZONE_MOVABLE];
|
|
|
|
return default_zone_for_pfn(nid, start_pfn, nr_pages);
|
|
}
|
|
|
|
/*
|
|
* Associates the given pfn range with the given node and the zone appropriate
|
|
* for the given online type.
|
|
*/
|
|
static struct zone * __meminit move_pfn_range(int online_type, int nid,
|
|
unsigned long start_pfn, unsigned long nr_pages)
|
|
{
|
|
struct zone *zone;
|
|
|
|
zone = zone_for_pfn_range(online_type, nid, start_pfn, nr_pages);
|
|
move_pfn_range_to_zone(zone, start_pfn, nr_pages, NULL);
|
|
return zone;
|
|
}
|
|
|
|
int __ref online_pages(unsigned long pfn, unsigned long nr_pages, int online_type)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long onlined_pages = 0;
|
|
struct zone *zone;
|
|
int need_zonelists_rebuild = 0;
|
|
int nid;
|
|
int ret;
|
|
struct memory_notify arg;
|
|
struct memory_block *mem;
|
|
|
|
mem_hotplug_begin();
|
|
|
|
/*
|
|
* We can't use pfn_to_nid() because nid might be stored in struct page
|
|
* which is not yet initialized. Instead, we find nid from memory block.
|
|
*/
|
|
mem = find_memory_block(__pfn_to_section(pfn));
|
|
nid = mem->nid;
|
|
put_device(&mem->dev);
|
|
|
|
/* associate pfn range with the zone */
|
|
zone = move_pfn_range(online_type, nid, pfn, nr_pages);
|
|
|
|
arg.start_pfn = pfn;
|
|
arg.nr_pages = nr_pages;
|
|
node_states_check_changes_online(nr_pages, zone, &arg);
|
|
|
|
ret = memory_notify(MEM_GOING_ONLINE, &arg);
|
|
ret = notifier_to_errno(ret);
|
|
if (ret)
|
|
goto failed_addition;
|
|
|
|
/*
|
|
* If this zone is not populated, then it is not in zonelist.
|
|
* This means the page allocator ignores this zone.
|
|
* So, zonelist must be updated after online.
|
|
*/
|
|
if (!populated_zone(zone)) {
|
|
need_zonelists_rebuild = 1;
|
|
setup_zone_pageset(zone);
|
|
}
|
|
|
|
ret = walk_system_ram_range(pfn, nr_pages, &onlined_pages,
|
|
online_pages_range);
|
|
if (ret) {
|
|
if (need_zonelists_rebuild)
|
|
zone_pcp_reset(zone);
|
|
goto failed_addition;
|
|
}
|
|
|
|
zone->present_pages += onlined_pages;
|
|
|
|
pgdat_resize_lock(zone->zone_pgdat, &flags);
|
|
zone->zone_pgdat->node_present_pages += onlined_pages;
|
|
pgdat_resize_unlock(zone->zone_pgdat, &flags);
|
|
|
|
if (onlined_pages) {
|
|
node_states_set_node(nid, &arg);
|
|
if (need_zonelists_rebuild)
|
|
build_all_zonelists(NULL);
|
|
else
|
|
zone_pcp_update(zone);
|
|
}
|
|
|
|
init_per_zone_wmark_min();
|
|
|
|
if (onlined_pages) {
|
|
kswapd_run(nid);
|
|
kcompactd_run(nid);
|
|
}
|
|
|
|
vm_total_pages = nr_free_pagecache_pages();
|
|
|
|
writeback_set_ratelimit();
|
|
|
|
if (onlined_pages)
|
|
memory_notify(MEM_ONLINE, &arg);
|
|
mem_hotplug_done();
|
|
return 0;
|
|
|
|
failed_addition:
|
|
pr_debug("online_pages [mem %#010llx-%#010llx] failed\n",
|
|
(unsigned long long) pfn << PAGE_SHIFT,
|
|
(((unsigned long long) pfn + nr_pages) << PAGE_SHIFT) - 1);
|
|
memory_notify(MEM_CANCEL_ONLINE, &arg);
|
|
remove_pfn_range_from_zone(zone, pfn, nr_pages);
|
|
mem_hotplug_done();
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_MEMORY_HOTPLUG_SPARSE */
|
|
|
|
static void reset_node_present_pages(pg_data_t *pgdat)
|
|
{
|
|
struct zone *z;
|
|
|
|
for (z = pgdat->node_zones; z < pgdat->node_zones + MAX_NR_ZONES; z++)
|
|
z->present_pages = 0;
|
|
|
|
pgdat->node_present_pages = 0;
|
|
}
|
|
|
|
/* we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG */
|
|
static pg_data_t __ref *hotadd_new_pgdat(int nid, u64 start)
|
|
{
|
|
struct pglist_data *pgdat;
|
|
unsigned long start_pfn = PFN_DOWN(start);
|
|
|
|
pgdat = NODE_DATA(nid);
|
|
if (!pgdat) {
|
|
pgdat = arch_alloc_nodedata(nid);
|
|
if (!pgdat)
|
|
return NULL;
|
|
|
|
arch_refresh_nodedata(nid, pgdat);
|
|
} else {
|
|
/*
|
|
* Reset the nr_zones, order and classzone_idx before reuse.
|
|
* Note that kswapd will init kswapd_classzone_idx properly
|
|
* when it starts in the near future.
|
|
*/
|
|
pgdat->nr_zones = 0;
|
|
pgdat->kswapd_order = 0;
|
|
pgdat->kswapd_classzone_idx = 0;
|
|
}
|
|
|
|
/* we can use NODE_DATA(nid) from here */
|
|
|
|
pgdat->node_id = nid;
|
|
pgdat->node_start_pfn = start_pfn;
|
|
|
|
/* init node's zones as empty zones, we don't have any present pages.*/
|
|
free_area_init_core_hotplug(nid);
|
|
pgdat->per_cpu_nodestats = alloc_percpu(struct per_cpu_nodestat);
|
|
|
|
/*
|
|
* The node we allocated has no zone fallback lists. For avoiding
|
|
* to access not-initialized zonelist, build here.
|
|
*/
|
|
build_all_zonelists(pgdat);
|
|
|
|
/*
|
|
* When memory is hot-added, all the memory is in offline state. So
|
|
* clear all zones' present_pages because they will be updated in
|
|
* online_pages() and offline_pages().
|
|
*/
|
|
reset_node_managed_pages(pgdat);
|
|
reset_node_present_pages(pgdat);
|
|
|
|
return pgdat;
|
|
}
|
|
|
|
static void rollback_node_hotadd(int nid)
|
|
{
|
|
pg_data_t *pgdat = NODE_DATA(nid);
|
|
|
|
arch_refresh_nodedata(nid, NULL);
|
|
free_percpu(pgdat->per_cpu_nodestats);
|
|
arch_free_nodedata(pgdat);
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* try_online_node - online a node if offlined
|
|
* @nid: the node ID
|
|
* @start: start addr of the node
|
|
* @set_node_online: Whether we want to online the node
|
|
* called by cpu_up() to online a node without onlined memory.
|
|
*
|
|
* Returns:
|
|
* 1 -> a new node has been allocated
|
|
* 0 -> the node is already online
|
|
* -ENOMEM -> the node could not be allocated
|
|
*/
|
|
static int __try_online_node(int nid, u64 start, bool set_node_online)
|
|
{
|
|
pg_data_t *pgdat;
|
|
int ret = 1;
|
|
|
|
if (node_online(nid))
|
|
return 0;
|
|
|
|
pgdat = hotadd_new_pgdat(nid, start);
|
|
if (!pgdat) {
|
|
pr_err("Cannot online node %d due to NULL pgdat\n", nid);
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (set_node_online) {
|
|
node_set_online(nid);
|
|
ret = register_one_node(nid);
|
|
BUG_ON(ret);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Users of this function always want to online/register the node
|
|
*/
|
|
int try_online_node(int nid)
|
|
{
|
|
int ret;
|
|
|
|
mem_hotplug_begin();
|
|
ret = __try_online_node(nid, 0, true);
|
|
mem_hotplug_done();
|
|
return ret;
|
|
}
|
|
|
|
static int online_memory_one_block(struct memory_block *mem, void *arg)
|
|
{
|
|
bool *onlined_block = (bool *)arg;
|
|
int ret;
|
|
|
|
if (*onlined_block || !is_memblock_offlined(mem))
|
|
return 0;
|
|
|
|
ret = device_online(&mem->dev);
|
|
if (!ret)
|
|
*onlined_block = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool try_online_one_block(int nid)
|
|
{
|
|
struct zone *zone = &NODE_DATA(nid)->node_zones[ZONE_MOVABLE];
|
|
bool onlined_block = false;
|
|
int ret = lock_device_hotplug_sysfs();
|
|
|
|
if (ret)
|
|
return false;
|
|
|
|
walk_memory_range(zone->zone_start_pfn, zone_end_pfn(zone),
|
|
&onlined_block, online_memory_one_block);
|
|
|
|
unlock_device_hotplug();
|
|
return onlined_block;
|
|
}
|
|
|
|
static int check_hotplug_memory_range(u64 start, u64 size)
|
|
{
|
|
unsigned long block_sz = memory_block_size_bytes();
|
|
u64 block_nr_pages = block_sz >> PAGE_SHIFT;
|
|
u64 nr_pages = size >> PAGE_SHIFT;
|
|
u64 start_pfn = PFN_DOWN(start);
|
|
|
|
/* memory range must be block size aligned */
|
|
if (!nr_pages || !IS_ALIGNED(start_pfn, block_nr_pages) ||
|
|
!IS_ALIGNED(nr_pages, block_nr_pages)) {
|
|
pr_err("Block size [%#lx] unaligned hotplug range: start %#llx, size %#llx",
|
|
block_sz, start, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int online_memory_block(struct memory_block *mem, void *arg)
|
|
{
|
|
return device_online(&mem->dev);
|
|
}
|
|
|
|
/*
|
|
* NOTE: The caller must call lock_device_hotplug() to serialize hotplug
|
|
* and online/offline operations (triggered e.g. by sysfs).
|
|
*
|
|
* we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG
|
|
*/
|
|
int __ref add_memory_resource(int nid, struct resource *res, bool online)
|
|
{
|
|
u64 start, size;
|
|
bool new_node = false;
|
|
int ret;
|
|
|
|
start = res->start;
|
|
size = resource_size(res);
|
|
|
|
ret = check_hotplug_memory_range(start, size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mem_hotplug_begin();
|
|
|
|
/*
|
|
* Add new range to memblock so that when hotadd_new_pgdat() is called
|
|
* to allocate new pgdat, get_pfn_range_for_nid() will be able to find
|
|
* this new range and calculate total pages correctly. The range will
|
|
* be removed at hot-remove time.
|
|
*/
|
|
memblock_add_node(start, size, nid);
|
|
|
|
ret = __try_online_node(nid, start, false);
|
|
if (ret < 0)
|
|
goto error;
|
|
new_node = ret;
|
|
|
|
/* call arch's memory hotadd */
|
|
ret = arch_add_memory(nid, start, size, NULL, true);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
/* create memory block devices after memory was added */
|
|
ret = create_memory_block_devices(start, size);
|
|
if (ret) {
|
|
arch_remove_memory(nid, start, size, NULL);
|
|
goto error;
|
|
}
|
|
|
|
if (new_node) {
|
|
/* If sysfs file of new node can't be created, cpu on the node
|
|
* can't be hot-added. There is no rollback way now.
|
|
* So, check by BUG_ON() to catch it reluctantly..
|
|
* We online node here. We can't roll back from here.
|
|
*/
|
|
node_set_online(nid);
|
|
ret = __register_one_node(nid);
|
|
BUG_ON(ret);
|
|
}
|
|
|
|
/* link memory sections under this node.*/
|
|
ret = link_mem_sections(nid, PFN_DOWN(start), PFN_UP(start + size - 1),
|
|
MEMINIT_HOTPLUG);
|
|
BUG_ON(ret);
|
|
|
|
/* create new memmap entry */
|
|
firmware_map_add_hotplug(start, start + size, "System RAM");
|
|
|
|
/* device_online() will take the lock when calling online_pages() */
|
|
mem_hotplug_done();
|
|
|
|
/* online pages if requested */
|
|
if (online)
|
|
walk_memory_range(PFN_DOWN(start), PFN_UP(start + size - 1),
|
|
NULL, online_memory_block);
|
|
|
|
return ret;
|
|
error:
|
|
/* rollback pgdat allocation and others */
|
|
if (new_node)
|
|
rollback_node_hotadd(nid);
|
|
memblock_remove(start, size);
|
|
mem_hotplug_done();
|
|
return ret;
|
|
}
|
|
|
|
/* requires device_hotplug_lock, see add_memory_resource() */
|
|
int __ref __add_memory(int nid, u64 start, u64 size)
|
|
{
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
res = register_memory_resource(start, size);
|
|
if (IS_ERR(res))
|
|
return PTR_ERR(res);
|
|
|
|
ret = add_memory_resource(nid, res, memhp_auto_online);
|
|
if (ret < 0)
|
|
release_memory_resource(res);
|
|
return ret;
|
|
}
|
|
|
|
int add_memory(int nid, u64 start, u64 size)
|
|
{
|
|
int rc;
|
|
|
|
lock_device_hotplug();
|
|
rc = __add_memory(nid, start, size);
|
|
unlock_device_hotplug();
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(add_memory);
|
|
|
|
#ifdef CONFIG_MEMORY_HOTREMOVE
|
|
/*
|
|
* A free page on the buddy free lists (not the per-cpu lists) has PageBuddy
|
|
* set and the size of the free page is given by page_order(). Using this,
|
|
* the function determines if the pageblock contains only free pages.
|
|
* Due to buddy contraints, a free page at least the size of a pageblock will
|
|
* be located at the start of the pageblock
|
|
*/
|
|
static inline int pageblock_free(struct page *page)
|
|
{
|
|
return PageBuddy(page) && page_order(page) >= pageblock_order;
|
|
}
|
|
|
|
/* Return the pfn of the start of the next active pageblock after a given pfn */
|
|
static unsigned long next_active_pageblock(unsigned long pfn)
|
|
{
|
|
struct page *page = pfn_to_page(pfn);
|
|
|
|
/* Ensure the starting page is pageblock-aligned */
|
|
BUG_ON(pfn & (pageblock_nr_pages - 1));
|
|
|
|
/* If the entire pageblock is free, move to the end of free page */
|
|
if (pageblock_free(page)) {
|
|
int order;
|
|
/* be careful. we don't have locks, page_order can be changed.*/
|
|
order = page_order(page);
|
|
if ((order < MAX_ORDER) && (order >= pageblock_order))
|
|
return pfn + (1 << order);
|
|
}
|
|
|
|
return pfn + pageblock_nr_pages;
|
|
}
|
|
|
|
static bool is_pageblock_removable_nolock(unsigned long pfn)
|
|
{
|
|
struct page *page = pfn_to_page(pfn);
|
|
struct zone *zone;
|
|
|
|
/*
|
|
* We have to be careful here because we are iterating over memory
|
|
* sections which are not zone aware so we might end up outside of
|
|
* the zone but still within the section.
|
|
* We have to take care about the node as well. If the node is offline
|
|
* its NODE_DATA will be NULL - see page_zone.
|
|
*/
|
|
if (!node_online(page_to_nid(page)))
|
|
return false;
|
|
|
|
zone = page_zone(page);
|
|
pfn = page_to_pfn(page);
|
|
if (!zone_spans_pfn(zone, pfn))
|
|
return false;
|
|
|
|
return !has_unmovable_pages(zone, page, 0, MIGRATE_MOVABLE, true);
|
|
}
|
|
|
|
/* Checks if this range of memory is likely to be hot-removable. */
|
|
bool is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages)
|
|
{
|
|
unsigned long end_pfn, pfn;
|
|
|
|
end_pfn = min(start_pfn + nr_pages,
|
|
zone_end_pfn(page_zone(pfn_to_page(start_pfn))));
|
|
|
|
/* Check the starting page of each pageblock within the range */
|
|
for (pfn = start_pfn; pfn < end_pfn; pfn = next_active_pageblock(pfn)) {
|
|
if (!is_pageblock_removable_nolock(pfn))
|
|
return false;
|
|
cond_resched();
|
|
}
|
|
|
|
/* All pageblocks in the memory block are likely to be hot-removable */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Confirm all pages in a range [start, end) belong to the same zone.
|
|
* When true, return its valid [start, end).
|
|
*/
|
|
int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned long *valid_start, unsigned long *valid_end)
|
|
{
|
|
unsigned long pfn, sec_end_pfn;
|
|
unsigned long start, end;
|
|
struct zone *zone = NULL;
|
|
struct page *page;
|
|
int i;
|
|
for (pfn = start_pfn, sec_end_pfn = SECTION_ALIGN_UP(start_pfn + 1);
|
|
pfn < end_pfn;
|
|
pfn = sec_end_pfn, sec_end_pfn += PAGES_PER_SECTION) {
|
|
/* Make sure the memory section is present first */
|
|
if (!present_section_nr(pfn_to_section_nr(pfn)))
|
|
continue;
|
|
for (; pfn < sec_end_pfn && pfn < end_pfn;
|
|
pfn += MAX_ORDER_NR_PAGES) {
|
|
i = 0;
|
|
/* This is just a CONFIG_HOLES_IN_ZONE check.*/
|
|
while ((i < MAX_ORDER_NR_PAGES) &&
|
|
!pfn_valid_within(pfn + i))
|
|
i++;
|
|
if (i == MAX_ORDER_NR_PAGES || pfn + i >= end_pfn)
|
|
continue;
|
|
/* Check if we got outside of the zone */
|
|
if (zone && !zone_spans_pfn(zone, pfn + i))
|
|
return 0;
|
|
page = pfn_to_page(pfn + i);
|
|
if (zone && page_zone(page) != zone)
|
|
return 0;
|
|
if (!zone)
|
|
start = pfn + i;
|
|
zone = page_zone(page);
|
|
end = pfn + MAX_ORDER_NR_PAGES;
|
|
}
|
|
}
|
|
|
|
if (zone) {
|
|
*valid_start = start;
|
|
*valid_end = min(end, end_pfn);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Scan pfn range [start,end) to find movable/migratable pages (LRU pages,
|
|
* non-lru movable pages and hugepages). We scan pfn because it's much
|
|
* easier than scanning over linked list. This function returns the pfn
|
|
* of the first found movable page if it's found, otherwise 0.
|
|
*/
|
|
static unsigned long scan_movable_pages(unsigned long start, unsigned long end)
|
|
{
|
|
unsigned long pfn;
|
|
|
|
for (pfn = start; pfn < end; pfn++) {
|
|
struct page *page, *head;
|
|
unsigned long skip;
|
|
|
|
if (!pfn_valid(pfn))
|
|
continue;
|
|
page = pfn_to_page(pfn);
|
|
if (PageLRU(page))
|
|
return pfn;
|
|
if (__PageMovable(page))
|
|
return pfn;
|
|
|
|
if (!PageHuge(page))
|
|
continue;
|
|
head = compound_head(page);
|
|
if (hugepage_migration_supported(page_hstate(head)) &&
|
|
page_huge_active(head))
|
|
return pfn;
|
|
skip = (1 << compound_order(head)) - (page - head);
|
|
pfn += skip - 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct page *new_node_page(struct page *page, unsigned long private)
|
|
{
|
|
int nid = page_to_nid(page);
|
|
nodemask_t nmask = node_states[N_MEMORY];
|
|
|
|
/*
|
|
* try to allocate from a different node but reuse this node if there
|
|
* are no other online nodes to be used (e.g. we are offlining a part
|
|
* of the only existing node)
|
|
*/
|
|
node_clear(nid, nmask);
|
|
if (nodes_empty(nmask))
|
|
node_set(nid, nmask);
|
|
|
|
return new_page_nodemask(page, nid, &nmask);
|
|
}
|
|
|
|
#define NR_OFFLINE_AT_ONCE_PAGES (256)
|
|
static int
|
|
do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)
|
|
{
|
|
unsigned long pfn;
|
|
struct page *page;
|
|
int move_pages = NR_OFFLINE_AT_ONCE_PAGES;
|
|
int not_managed = 0;
|
|
int ret = 0;
|
|
LIST_HEAD(source);
|
|
|
|
for (pfn = start_pfn; pfn < end_pfn && move_pages > 0; pfn++) {
|
|
if (!pfn_valid(pfn))
|
|
continue;
|
|
page = pfn_to_page(pfn);
|
|
|
|
if (PageHuge(page)) {
|
|
struct page *head = compound_head(page);
|
|
pfn = page_to_pfn(head) + (1<<compound_order(head)) - 1;
|
|
if (compound_order(head) > PFN_SECTION_SHIFT) {
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
if (isolate_huge_page(page, &source))
|
|
move_pages -= 1 << compound_order(head);
|
|
continue;
|
|
} else if (PageTransHuge(page))
|
|
pfn = page_to_pfn(compound_head(page))
|
|
+ hpage_nr_pages(page) - 1;
|
|
|
|
/*
|
|
* HWPoison pages have elevated reference counts so the migration would
|
|
* fail on them. It also doesn't make any sense to migrate them in the
|
|
* first place. Still try to unmap such a page in case it is still mapped
|
|
* (e.g. current hwpoison implementation doesn't unmap KSM pages but keep
|
|
* the unmap as the catch all safety net).
|
|
*/
|
|
if (PageHWPoison(page)) {
|
|
if (WARN_ON(PageLRU(page)))
|
|
isolate_lru_page(page);
|
|
if (page_mapped(page))
|
|
try_to_unmap(page, TTU_IGNORE_MLOCK | TTU_IGNORE_ACCESS);
|
|
continue;
|
|
}
|
|
|
|
if (!get_page_unless_zero(page))
|
|
continue;
|
|
/*
|
|
* We can skip free pages. And we can deal with pages on
|
|
* LRU and non-lru movable pages.
|
|
*/
|
|
if (PageLRU(page))
|
|
ret = isolate_lru_page(page);
|
|
else
|
|
ret = isolate_movable_page(page, ISOLATE_UNEVICTABLE);
|
|
if (!ret) { /* Success */
|
|
put_page(page);
|
|
list_add_tail(&page->lru, &source);
|
|
move_pages--;
|
|
if (!__PageMovable(page))
|
|
inc_node_page_state(page, NR_ISOLATED_ANON +
|
|
page_is_file_cache(page));
|
|
|
|
} else {
|
|
#ifdef CONFIG_DEBUG_VM
|
|
pr_alert("failed to isolate pfn %lx\n", pfn);
|
|
dump_page(page, "isolation failed");
|
|
#endif
|
|
put_page(page);
|
|
/* Because we don't have big zone->lock. we should
|
|
check this again here. */
|
|
if (page_count(page)) {
|
|
not_managed++;
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!list_empty(&source)) {
|
|
if (not_managed) {
|
|
putback_movable_pages(&source);
|
|
goto out;
|
|
}
|
|
|
|
/* Allocate a new page from the nearest neighbor node */
|
|
ret = migrate_pages(&source, new_node_page, NULL, 0,
|
|
MIGRATE_SYNC, MR_MEMORY_HOTPLUG);
|
|
if (ret)
|
|
putback_movable_pages(&source);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* remove from free_area[] and mark all as Reserved.
|
|
*/
|
|
static int
|
|
offline_isolated_pages_cb(unsigned long start, unsigned long nr_pages,
|
|
void *data)
|
|
{
|
|
__offline_isolated_pages(start, start + nr_pages);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
offline_isolated_pages(unsigned long start_pfn, unsigned long end_pfn)
|
|
{
|
|
walk_system_ram_range(start_pfn, end_pfn - start_pfn, NULL,
|
|
offline_isolated_pages_cb);
|
|
}
|
|
|
|
/*
|
|
* Check all pages in range, recoreded as memory resource, are isolated.
|
|
*/
|
|
static int
|
|
check_pages_isolated_cb(unsigned long start_pfn, unsigned long nr_pages,
|
|
void *data)
|
|
{
|
|
int ret;
|
|
long offlined = *(long *)data;
|
|
ret = test_pages_isolated(start_pfn, start_pfn + nr_pages, true);
|
|
offlined = nr_pages;
|
|
if (!ret)
|
|
*(long *)data += offlined;
|
|
return ret;
|
|
}
|
|
|
|
static long
|
|
check_pages_isolated(unsigned long start_pfn, unsigned long end_pfn)
|
|
{
|
|
long offlined = 0;
|
|
int ret;
|
|
|
|
ret = walk_system_ram_range(start_pfn, end_pfn - start_pfn, &offlined,
|
|
check_pages_isolated_cb);
|
|
if (ret < 0)
|
|
offlined = (long)ret;
|
|
return offlined;
|
|
}
|
|
|
|
static int __init cmdline_parse_movable_node(char *p)
|
|
{
|
|
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
|
|
movable_node_enabled = true;
|
|
#else
|
|
pr_warn("movable_node parameter depends on CONFIG_HAVE_MEMBLOCK_NODE_MAP to work properly\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
early_param("movable_node", cmdline_parse_movable_node);
|
|
|
|
/* check which state of node_states will be changed when offline memory */
|
|
static void node_states_check_changes_offline(unsigned long nr_pages,
|
|
struct zone *zone, struct memory_notify *arg)
|
|
{
|
|
struct pglist_data *pgdat = zone->zone_pgdat;
|
|
unsigned long present_pages = 0;
|
|
enum zone_type zt, zone_last = ZONE_NORMAL;
|
|
|
|
/*
|
|
* If we have HIGHMEM or movable node, node_states[N_NORMAL_MEMORY]
|
|
* contains nodes which have zones of 0...ZONE_NORMAL,
|
|
* set zone_last to ZONE_NORMAL.
|
|
*
|
|
* If we don't have HIGHMEM nor movable node,
|
|
* node_states[N_NORMAL_MEMORY] contains nodes which have zones of
|
|
* 0...ZONE_MOVABLE, set zone_last to ZONE_MOVABLE.
|
|
*/
|
|
if (N_MEMORY == N_NORMAL_MEMORY)
|
|
zone_last = ZONE_MOVABLE;
|
|
|
|
/*
|
|
* check whether node_states[N_NORMAL_MEMORY] will be changed.
|
|
* If the memory to be offline is in a zone of 0...zone_last,
|
|
* and it is the last present memory, 0...zone_last will
|
|
* become empty after offline , thus we can determind we will
|
|
* need to clear the node from node_states[N_NORMAL_MEMORY].
|
|
*/
|
|
for (zt = 0; zt <= zone_last; zt++)
|
|
present_pages += pgdat->node_zones[zt].present_pages;
|
|
if (zone_idx(zone) <= zone_last && nr_pages >= present_pages)
|
|
arg->status_change_nid_normal = zone_to_nid(zone);
|
|
else
|
|
arg->status_change_nid_normal = -1;
|
|
|
|
#ifdef CONFIG_HIGHMEM
|
|
/*
|
|
* If we have movable node, node_states[N_HIGH_MEMORY]
|
|
* contains nodes which have zones of 0...ZONE_HIGHMEM,
|
|
* set zone_last to ZONE_HIGHMEM.
|
|
*
|
|
* If we don't have movable node, node_states[N_NORMAL_MEMORY]
|
|
* contains nodes which have zones of 0...ZONE_MOVABLE,
|
|
* set zone_last to ZONE_MOVABLE.
|
|
*/
|
|
zone_last = ZONE_HIGHMEM;
|
|
if (N_MEMORY == N_HIGH_MEMORY)
|
|
zone_last = ZONE_MOVABLE;
|
|
|
|
for (; zt <= zone_last; zt++)
|
|
present_pages += pgdat->node_zones[zt].present_pages;
|
|
if (zone_idx(zone) <= zone_last && nr_pages >= present_pages)
|
|
arg->status_change_nid_high = zone_to_nid(zone);
|
|
else
|
|
arg->status_change_nid_high = -1;
|
|
#else
|
|
arg->status_change_nid_high = arg->status_change_nid_normal;
|
|
#endif
|
|
|
|
/*
|
|
* node_states[N_HIGH_MEMORY] contains nodes which have 0...ZONE_MOVABLE
|
|
*/
|
|
zone_last = ZONE_MOVABLE;
|
|
|
|
/*
|
|
* check whether node_states[N_HIGH_MEMORY] will be changed
|
|
* If we try to offline the last present @nr_pages from the node,
|
|
* we can determind we will need to clear the node from
|
|
* node_states[N_HIGH_MEMORY].
|
|
*/
|
|
for (; zt <= zone_last; zt++)
|
|
present_pages += pgdat->node_zones[zt].present_pages;
|
|
if (nr_pages >= present_pages)
|
|
arg->status_change_nid = zone_to_nid(zone);
|
|
else
|
|
arg->status_change_nid = -1;
|
|
}
|
|
|
|
static void node_states_clear_node(int node, struct memory_notify *arg)
|
|
{
|
|
if (arg->status_change_nid_normal >= 0)
|
|
node_clear_state(node, N_NORMAL_MEMORY);
|
|
|
|
if ((N_MEMORY != N_NORMAL_MEMORY) &&
|
|
(arg->status_change_nid_high >= 0))
|
|
node_clear_state(node, N_HIGH_MEMORY);
|
|
|
|
if ((N_MEMORY != N_HIGH_MEMORY) &&
|
|
(arg->status_change_nid >= 0))
|
|
node_clear_state(node, N_MEMORY);
|
|
}
|
|
|
|
static int __ref __offline_pages(unsigned long start_pfn,
|
|
unsigned long end_pfn)
|
|
{
|
|
unsigned long pfn, nr_pages;
|
|
long offlined_pages;
|
|
int ret, node;
|
|
unsigned long flags;
|
|
unsigned long valid_start, valid_end;
|
|
struct zone *zone;
|
|
struct memory_notify arg;
|
|
|
|
/* at least, alignment against pageblock is necessary */
|
|
if (!IS_ALIGNED(start_pfn, pageblock_nr_pages))
|
|
return -EINVAL;
|
|
if (!IS_ALIGNED(end_pfn, pageblock_nr_pages))
|
|
return -EINVAL;
|
|
|
|
mem_hotplug_begin();
|
|
|
|
/* This makes hotplug much easier...and readable.
|
|
we assume this for now. .*/
|
|
if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start,
|
|
&valid_end)) {
|
|
mem_hotplug_done();
|
|
return -EINVAL;
|
|
}
|
|
|
|
zone = page_zone(pfn_to_page(valid_start));
|
|
node = zone_to_nid(zone);
|
|
nr_pages = end_pfn - start_pfn;
|
|
|
|
/* set above range as isolated */
|
|
ret = start_isolate_page_range(start_pfn, end_pfn,
|
|
MIGRATE_MOVABLE, true);
|
|
if (ret) {
|
|
mem_hotplug_done();
|
|
return ret;
|
|
}
|
|
|
|
arg.start_pfn = start_pfn;
|
|
arg.nr_pages = nr_pages;
|
|
node_states_check_changes_offline(nr_pages, zone, &arg);
|
|
|
|
ret = memory_notify(MEM_GOING_OFFLINE, &arg);
|
|
ret = notifier_to_errno(ret);
|
|
if (ret)
|
|
goto failed_removal;
|
|
|
|
pfn = start_pfn;
|
|
repeat:
|
|
/* start memory hot removal */
|
|
ret = -EINTR;
|
|
if (signal_pending(current))
|
|
goto failed_removal;
|
|
|
|
cond_resched();
|
|
lru_add_drain_all();
|
|
drain_all_pages(zone);
|
|
|
|
pfn = scan_movable_pages(start_pfn, end_pfn);
|
|
if (pfn) { /* We have movable pages */
|
|
ret = do_migrate_range(pfn, end_pfn);
|
|
goto repeat;
|
|
}
|
|
|
|
/*
|
|
* dissolve free hugepages in the memory block before doing offlining
|
|
* actually in order to make hugetlbfs's object counting consistent.
|
|
*/
|
|
ret = dissolve_free_huge_pages(start_pfn, end_pfn);
|
|
if (ret)
|
|
goto failed_removal;
|
|
/* check again */
|
|
offlined_pages = check_pages_isolated(start_pfn, end_pfn);
|
|
if (offlined_pages < 0)
|
|
goto repeat;
|
|
pr_info("Offlined Pages %ld\n", offlined_pages);
|
|
/* Ok, all of our target is isolated.
|
|
We cannot do rollback at this point. */
|
|
offline_isolated_pages(start_pfn, end_pfn);
|
|
/* reset pagetype flags and makes migrate type to be MOVABLE */
|
|
undo_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
|
|
/* removal success */
|
|
adjust_managed_page_count(pfn_to_page(start_pfn), -offlined_pages);
|
|
zone->present_pages -= offlined_pages;
|
|
|
|
pgdat_resize_lock(zone->zone_pgdat, &flags);
|
|
zone->zone_pgdat->node_present_pages -= offlined_pages;
|
|
pgdat_resize_unlock(zone->zone_pgdat, &flags);
|
|
|
|
init_per_zone_wmark_min();
|
|
|
|
if (!populated_zone(zone)) {
|
|
zone_pcp_reset(zone);
|
|
build_all_zonelists(NULL);
|
|
} else
|
|
zone_pcp_update(zone);
|
|
|
|
node_states_clear_node(node, &arg);
|
|
if (arg.status_change_nid >= 0) {
|
|
kswapd_stop(node);
|
|
kcompactd_stop(node);
|
|
}
|
|
|
|
vm_total_pages = nr_free_pagecache_pages();
|
|
writeback_set_ratelimit();
|
|
|
|
memory_notify(MEM_OFFLINE, &arg);
|
|
remove_pfn_range_from_zone(zone, start_pfn, nr_pages);
|
|
mem_hotplug_done();
|
|
return 0;
|
|
|
|
failed_removal:
|
|
pr_debug("memory offlining [mem %#010llx-%#010llx] failed\n",
|
|
(unsigned long long) start_pfn << PAGE_SHIFT,
|
|
((unsigned long long) end_pfn << PAGE_SHIFT) - 1);
|
|
memory_notify(MEM_CANCEL_OFFLINE, &arg);
|
|
/* pushback to free area */
|
|
undo_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
|
|
mem_hotplug_done();
|
|
return ret;
|
|
}
|
|
|
|
int offline_pages(unsigned long start_pfn, unsigned long nr_pages)
|
|
{
|
|
return __offline_pages(start_pfn, start_pfn + nr_pages);
|
|
}
|
|
#endif /* CONFIG_MEMORY_HOTREMOVE */
|
|
|
|
/**
|
|
* walk_memory_range - walks through all mem sections in [start_pfn, end_pfn)
|
|
* @start_pfn: start pfn of the memory range
|
|
* @end_pfn: end pfn of the memory range
|
|
* @arg: argument passed to func
|
|
* @func: callback for each memory section walked
|
|
*
|
|
* This function walks through all present mem sections in range
|
|
* [start_pfn, end_pfn) and call func on each mem section.
|
|
*
|
|
* Returns the return value of func.
|
|
*/
|
|
int walk_memory_range(unsigned long start_pfn, unsigned long end_pfn,
|
|
void *arg, int (*func)(struct memory_block *, void *))
|
|
{
|
|
struct memory_block *mem = NULL;
|
|
struct mem_section *section;
|
|
unsigned long pfn, section_nr;
|
|
int ret;
|
|
|
|
for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
|
|
section_nr = pfn_to_section_nr(pfn);
|
|
if (!present_section_nr(section_nr))
|
|
continue;
|
|
|
|
section = __nr_to_section(section_nr);
|
|
/* same memblock? */
|
|
if (mem)
|
|
if ((section_nr >= mem->start_section_nr) &&
|
|
(section_nr <= mem->end_section_nr))
|
|
continue;
|
|
|
|
mem = find_memory_block_hinted(section, mem);
|
|
if (!mem)
|
|
continue;
|
|
|
|
ret = func(mem, arg);
|
|
if (ret) {
|
|
kobject_put(&mem->dev.kobj);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (mem)
|
|
kobject_put(&mem->dev.kobj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_MEMORY_HOTREMOVE
|
|
static int check_memblock_offlined_cb(struct memory_block *mem, void *arg)
|
|
{
|
|
int ret = !is_memblock_offlined(mem);
|
|
|
|
if (unlikely(ret)) {
|
|
phys_addr_t beginpa, endpa;
|
|
|
|
beginpa = PFN_PHYS(section_nr_to_pfn(mem->start_section_nr));
|
|
endpa = PFN_PHYS(section_nr_to_pfn(mem->end_section_nr + 1))-1;
|
|
pr_warn("removing memory fails, because memory [%pa-%pa] is onlined\n",
|
|
&beginpa, &endpa);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int check_cpu_on_node(pg_data_t *pgdat)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_present_cpu(cpu) {
|
|
if (cpu_to_node(cpu) == pgdat->node_id)
|
|
/*
|
|
* the cpu on this node isn't removed, and we can't
|
|
* offline this node.
|
|
*/
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void unmap_cpu_on_node(pg_data_t *pgdat)
|
|
{
|
|
#ifdef CONFIG_ACPI_NUMA
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu)
|
|
if (cpu_to_node(cpu) == pgdat->node_id)
|
|
numa_clear_node(cpu);
|
|
#endif
|
|
}
|
|
|
|
static int check_and_unmap_cpu_on_node(pg_data_t *pgdat)
|
|
{
|
|
int ret;
|
|
|
|
ret = check_cpu_on_node(pgdat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* the node will be offlined when we come here, so we can clear
|
|
* the cpu_to_node() now.
|
|
*/
|
|
|
|
unmap_cpu_on_node(pgdat);
|
|
return 0;
|
|
}
|
|
|
|
static int check_no_memblock_for_node_cb(struct memory_block *mem, void *arg)
|
|
{
|
|
int nid = *(int *)arg;
|
|
|
|
/*
|
|
* If a memory block belongs to multiple nodes, the stored nid is not
|
|
* reliable. However, such blocks are always online (e.g., cannot get
|
|
* offlined) and, therefore, are still spanned by the node.
|
|
*/
|
|
return mem->nid == nid ? -EEXIST : 0;
|
|
}
|
|
|
|
/**
|
|
* try_offline_node
|
|
* @nid: the node ID
|
|
*
|
|
* Offline a node if all memory sections and cpus of the node are removed.
|
|
*
|
|
* NOTE: The caller must call lock_device_hotplug() to serialize hotplug
|
|
* and online/offline operations before this call.
|
|
*/
|
|
void try_offline_node(int nid)
|
|
{
|
|
pg_data_t *pgdat = NODE_DATA(nid);
|
|
int rc;
|
|
|
|
/*
|
|
* If the node still spans pages (especially ZONE_DEVICE), don't
|
|
* offline it. A node spans memory after move_pfn_range_to_zone(),
|
|
* e.g., after the memory block was onlined.
|
|
*/
|
|
if (pgdat->node_spanned_pages)
|
|
return;
|
|
|
|
/*
|
|
* Especially offline memory blocks might not be spanned by the
|
|
* node. They will get spanned by the node once they get onlined.
|
|
* However, they link to the node in sysfs and can get onlined later.
|
|
*/
|
|
rc = for_each_memory_block(&nid, check_no_memblock_for_node_cb);
|
|
if (rc)
|
|
return;
|
|
|
|
if (check_and_unmap_cpu_on_node(pgdat))
|
|
return;
|
|
|
|
/*
|
|
* all memory/cpu of this node are removed, we can offline this
|
|
* node now.
|
|
*/
|
|
node_set_offline(nid);
|
|
unregister_one_node(nid);
|
|
}
|
|
EXPORT_SYMBOL(try_offline_node);
|
|
|
|
static void __release_memory_resource(resource_size_t start,
|
|
resource_size_t size)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* When removing memory in the same granularity as it was added,
|
|
* this function never fails. It might only fail if resources
|
|
* have to be adjusted or split. We'll ignore the error, as
|
|
* removing of memory cannot fail.
|
|
*/
|
|
ret = release_mem_region_adjustable(&iomem_resource, start, size);
|
|
if (ret) {
|
|
resource_size_t endres = start + size - 1;
|
|
|
|
pr_warn("Unable to release resource <%pa-%pa> (%d)\n",
|
|
&start, &endres, ret);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* remove_memory
|
|
* @nid: the node ID
|
|
* @start: physical address of the region to remove
|
|
* @size: size of the region to remove
|
|
*
|
|
* NOTE: The caller must call lock_device_hotplug() to serialize hotplug
|
|
* and online/offline operations before this call, as required by
|
|
* try_offline_node().
|
|
*/
|
|
void __ref __remove_memory(int nid, u64 start, u64 size)
|
|
{
|
|
int ret;
|
|
|
|
BUG_ON(check_hotplug_memory_range(start, size));
|
|
|
|
/*
|
|
* All memory blocks must be offlined before removing memory. Check
|
|
* whether all memory blocks in question are offline and trigger a BUG()
|
|
* if this is not the case.
|
|
*/
|
|
ret = walk_memory_range(PFN_DOWN(start), PFN_UP(start + size - 1), NULL,
|
|
check_memblock_offlined_cb);
|
|
if (ret)
|
|
BUG();
|
|
|
|
/* remove memmap entry */
|
|
firmware_map_remove(start, start + size, "System RAM");
|
|
memblock_free(start, size);
|
|
memblock_remove(start, size);
|
|
|
|
/*
|
|
* Memory block device removal under the device_hotplug_lock is
|
|
* a barrier against racing online attempts.
|
|
*/
|
|
remove_memory_block_devices(start, size);
|
|
|
|
mem_hotplug_begin();
|
|
|
|
arch_remove_memory(nid, start, size, NULL);
|
|
__release_memory_resource(start, size);
|
|
|
|
try_offline_node(nid);
|
|
|
|
mem_hotplug_done();
|
|
}
|
|
|
|
void remove_memory(int nid, u64 start, u64 size)
|
|
{
|
|
lock_device_hotplug();
|
|
__remove_memory(nid, start, size);
|
|
unlock_device_hotplug();
|
|
}
|
|
EXPORT_SYMBOL_GPL(remove_memory);
|
|
#endif /* CONFIG_MEMORY_HOTREMOVE */
|