Changes in 5.10.231
arm64: dts: allwinner: pinephone: Add mount matrix to accelerometer
media: i2c: tc358743: Fix crash in the probe error path when using polling
media: ts2020: fix null-ptr-deref in ts2020_probe()
media: venus: Fix pm_runtime_set_suspended() with runtime pm enabled
media: gspca: ov534-ov772x: Fix off-by-one error in set_frame_rate()
media: platform: allegro-dvt: Fix possible memory leak in allocate_buffers_internal()
media: uvcvideo: Stop stream during unregister
ovl: Filter invalid inodes with missing lookup function
ftrace: Fix regression with module command in stack_trace_filter
leds: lp55xx: Remove redundant test for invalid channel number
clk: qcom: gcc-qcs404: fix initial rate of GPLL3
netlink: terminate outstanding dump on socket close
net/mlx5: fs, lock FTE when checking if active
net/mlx5e: kTLS, Fix incorrect page refcounting
x86/mm: Fix a kdump kernel failure on SME system when CONFIG_IMA_KEXEC=y
ocfs2: uncache inode which has failed entering the group
vdpa/mlx5: Fix PA offset with unaligned starting iotlb map
KVM: VMX: Bury Intel PT virtualization (guest/host mode) behind CONFIG_BROKEN
nilfs2: fix null-ptr-deref in block_touch_buffer tracepoint
ocfs2: fix UBSAN warning in ocfs2_verify_volume()
nilfs2: fix null-ptr-deref in block_dirty_buffer tracepoint
Revert "mmc: dw_mmc: Fix IDMAC operation with pages bigger than 4K"
drm/bridge: tc358768: Fix DSI command tx
mmc: core: fix return value check in devm_mmc_alloc_host()
media: dvbdev: fix the logic when DVB_DYNAMIC_MINORS is not set
NFSD: initialize copy->cp_clp early in nfsd4_copy for use by trace point
NFSD: Async COPY result needs to return a write verifier
NFSD: Limit the number of concurrent async COPY operations
NFSD: Initialize struct nfsd4_copy earlier
NFSD: Never decrement pending_async_copies on error
mm: revert "mm: shmem: fix data-race in shmem_getattr()"
mm: avoid unsafe VMA hook invocation when error arises on mmap hook
mm: unconditionally close VMAs on error
mm: refactor arch_calc_vm_flag_bits() and arm64 MTE handling
mm: resolve faulty mmap_region() error path behaviour
ASoC: Intel: bytcr_rt5640: Add DMI quirk for Vexia Edu Atla 10 tablet
mac80211: fix user-power when emulating chanctx
selftests/watchdog-test: Fix system accidentally reset after watchdog-test
ALSA: hda/realtek: Add subwoofer quirk for Infinix ZERO BOOK 13
x86/amd_nb: Fix compile-testing without CONFIG_AMD_NB
net: usb: qmi_wwan: add Quectel RG650V
soc: qcom: Add check devm_kasprintf() returned value
regulator: rk808: Add apply_bit for BUCK3 on RK809
can: j1939: fix error in J1939 documentation.
ASoC: stm: Prevent potential division by zero in stm32_sai_mclk_round_rate()
ASoC: stm: Prevent potential division by zero in stm32_sai_get_clk_div()
proc/softirqs: replace seq_printf with seq_put_decimal_ull_width
ALSA: usb-audio: Fix Yamaha P-125 Quirk Entry
ipmr: Fix access to mfc_cache_list without lock held
rcu-tasks: Idle tasks on offline CPUs are in quiescent states
cifs: Fix buffer overflow when parsing NFS reparse points
nvme: fix metadata handling in nvme-passthrough
x86/barrier: Do not serialize MSR accesses on AMD
kselftest/arm64: mte: fix printf type warnings about longs
x86/xen/pvh: Annotate indirect branch as safe
mips: asm: fix warning when disabling MIPS_FP_SUPPORT
initramfs: avoid filename buffer overrun
nvme-pci: fix freeing of the HMB descriptor table
m68k: mvme147: Fix SCSI controller IRQ numbers
m68k: mvme16x: Add and use "mvme16x.h"
m68k: mvme147: Reinstate early console
arm64: fix .data.rel.ro size assertion when CONFIG_LTO_CLANG
acpi/arm64: Adjust error handling procedure in gtdt_parse_timer_block()
s390/syscalls: Avoid creation of arch/arch/ directory
hfsplus: don't query the device logical block size multiple times
crypto: caam - Fix the pointer passed to caam_qi_shutdown()
firmware: google: Unregister driver_info on failure
EDAC/bluefield: Fix potential integer overflow
EDAC/fsl_ddr: Fix bad bit shift operations
crypto: pcrypt - Call crypto layer directly when padata_do_parallel() return -EBUSY
crypto: cavium - Fix the if condition to exit loop after timeout
crypto: caam - add error check to caam_rsa_set_priv_key_form
crypto: bcm - add error check in the ahash_hmac_init function
crypto: cavium - Fix an error handling path in cpt_ucode_load_fw()
time: Fix references to _msecs_to_jiffies() handling of values
kcsan, seqlock: Fix incorrect assumption in read_seqbegin()
clkdev: remove CONFIG_CLKDEV_LOOKUP
clocksource/drivers:sp804: Make user selectable
spi: spi-fsl-lpspi: downgrade log level for pio mode
spi: spi-fsl-lpspi: Use IRQF_NO_AUTOEN flag in request_irq()
soc: ti: smartreflex: Use IRQF_NO_AUTOEN flag in request_irq()
soc: qcom: geni-se: fix array underflow in geni_se_clk_tbl_get()
mmc: mmc_spi: drop buggy snprintf()
tpm: fix signed/unsigned bug when checking event logs
arm64: dts: mt8183: krane: Fix the address of eeprom at i2c4
arm64: dts: mediatek: mt8173-elm-hana: Add vdd-supply to second source trackpad
Revert "cgroup: Fix memory leak caused by missing cgroup_bpf_offline"
cgroup/bpf: only cgroup v2 can be attached by bpf programs
pwm: imx27: Workaround of the pwm output bug when decrease the duty cycle
ARM: dts: cubieboard4: Fix DCDC5 regulator constraints
pmdomain: ti-sci: Add missing of_node_put() for args.np
regmap: irq: Set lockdep class for hierarchical IRQ domains
selftests/resctrl: Protect against array overrun during iMC config parsing
firmware: arm_scpi: Check the DVFS OPP count returned by the firmware
media: atomisp: remove #ifdef HAS_NO_HMEM
media: atomisp: Add check for rgby_data memory allocation failure
drm/mm: Mark drm_mm_interval_tree*() functions with __maybe_unused
wifi: ath9k: add range check for conn_rsp_epid in htc_connect_service()
drm/omap: Fix locking in omap_gem_new_dmabuf()
wifi: p54: Use IRQF_NO_AUTOEN flag in request_irq()
wifi: mwifiex: Use IRQF_NO_AUTOEN flag in request_irq()
drm/imx/dcss: Use IRQF_NO_AUTOEN flag in request_irq()
drm/imx/ipuv3: Use IRQF_NO_AUTOEN flag in request_irq()
drm/v3d: Address race-condition in MMU flush
wifi: ath10k: fix invalid VHT parameters in supported_vht_mcs_rate_nss1
wifi: ath10k: fix invalid VHT parameters in supported_vht_mcs_rate_nss2
dt-bindings: vendor-prefixes: Add NeoFidelity, Inc
ASoC: fsl_micfil: Drop unnecessary register read
ASoC: fsl_micfil: do not define SHIFT/MASK for single bits
ASoC: fsl_micfil: use GENMASK to define register bit fields
ASoC: fsl_micfil: fix regmap_write_bits usage
ASoC: dt-bindings: mt6359: Update generic node name and dmic-mode
bpf: Fix the xdp_adjust_tail sample prog issue
xfrm: rename xfrm_state_offload struct to allow reuse
xfrm: store and rely on direction to construct offload flags
netdevsim: rely on XFRM state direction instead of flags
netdevsim: copy addresses for both in and out paths
drm/bridge: tc358767: Fix link properties discovery
selftests/bpf: Fix msg_verify_data in test_sockmap
selftests/bpf: Fix txmsg_redir of test_txmsg_pull in test_sockmap
wifi: mwifiex: Fix memcpy() field-spanning write warning in mwifiex_config_scan()
drm/fsl-dcu: Convert to Linux IRQ interfaces
drm: fsl-dcu: enable PIXCLK on LS1021A
octeontx2-af: Mbox changes for 98xx
octeontx2-pf: Calculate LBK link instead of hardcoding
octeontx2-af: forward error correction configuration
octeontx2-af: Add new CGX_CMD to get PHY FEC statistics
octeontx2-pf: ethtool fec mode support
octeontx2-pf: handle otx2_mbox_get_rsp errors in otx2_ethtool.c
drm/panfrost: Remove unused id_mask from struct panfrost_model
drm/msm/adreno: Use IRQF_NO_AUTOEN flag in request_irq()
drm/etnaviv: rework linear window offset calculation
drm/etnaviv: Request pages from DMA32 zone on addressing_limited
drm/etnaviv: dump: fix sparse warnings
drm/etnaviv: fix power register offset on GC300
drm/etnaviv: hold GPU lock across perfmon sampling
wifi: wfx: Fix error handling in wfx_core_init()
drm/msm/dpu: cast crtc_clk calculation to u64 in _dpu_core_perf_calc_clk()
netlink: typographical error in nlmsg_type constants definition
selftests/bpf: Add txmsg_pass to pull/push/pop in test_sockmap
selftests/bpf: Fix SENDPAGE data logic in test_sockmap
selftests, bpf: Add one test for sockmap with strparser
selftests/bpf: Fix total_bytes in msg_loop_rx in test_sockmap
selftests/bpf: Add push/pop checking for msg_verify_data in test_sockmap
bpf, sockmap: Several fixes to bpf_msg_push_data
bpf, sockmap: Several fixes to bpf_msg_pop_data
bpf, sockmap: Fix sk_msg_reset_curr
selftests: net: really check for bg process completion
drm/amdkfd: Fix wrong usage of INIT_WORK()
net: rfkill: gpio: Add check for clk_enable()
ALSA: usx2y: Fix spaces
ALSA: usx2y: Coding style fixes
ALSA: usx2y: Cleanup probe and disconnect callbacks
ALSA: usx2y: Use snd_card_free_when_closed() at disconnection
ALSA: us122l: Use snd_card_free_when_closed() at disconnection
ALSA: caiaq: Use snd_card_free_when_closed() at disconnection
ALSA: 6fire: Release resources at card release
driver core: Introduce device_find_any_child() helper
Bluetooth: fix use-after-free in device_for_each_child()
netpoll: Use rcu_access_pointer() in netpoll_poll_lock
wireguard: selftests: load nf_conntrack if not present
trace/trace_event_perf: remove duplicate samples on the first tracepoint event
powerpc/vdso: Flag VDSO64 entry points as functions
mfd: tps65010: Use IRQF_NO_AUTOEN flag in request_irq() to fix race
mfd: da9052-spi: Change read-mask to write-mask
mfd: intel_soc_pmic_bxtwc: Use dev_err_probe()
mfd: intel_soc_pmic_bxtwc: Use IRQ domain for USB Type-C device
mfd: intel_soc_pmic_bxtwc: Use IRQ domain for TMU device
mfd: intel_soc_pmic_bxtwc: Use IRQ domain for PMIC devices
cpufreq: loongson2: Unregister platform_driver on failure
mtd: rawnand: atmel: Fix possible memory leak
powerpc/pseries: Fix dtl_access_lock to be a rw_semaphore
RDMA/bnxt_re: Check cqe flags to know imm_data vs inv_irkey
mfd: rt5033: Fix missing regmap_del_irq_chip()
scsi: bfa: Fix use-after-free in bfad_im_module_exit()
scsi: fusion: Remove unused variable 'rc'
scsi: qedf: Fix a possible memory leak in qedf_alloc_and_init_sb()
scsi: qedi: Fix a possible memory leak in qedi_alloc_and_init_sb()
RDMA/hns: Fix NULL pointer derefernce in hns_roce_map_mr_sg()
ocfs2: fix uninitialized value in ocfs2_file_read_iter()
powerpc/sstep: make emulate_vsx_load and emulate_vsx_store static
powerpc/kexec: Fix return of uninitialized variable
fbdev/sh7760fb: Alloc DMA memory from hardware device
fbdev: sh7760fb: Fix a possible memory leak in sh7760fb_alloc_mem()
dt-bindings: clock: adi,axi-clkgen: convert old binding to yaml format
dt-bindings: clock: axi-clkgen: include AXI clk
clk: axi-clkgen: use devm_platform_ioremap_resource() short-hand
clk: clk-axi-clkgen: make sure to enable the AXI bus clock
perf cs-etm: Don't flush when packet_queue fills up
perf probe: Fix libdw memory leak
perf probe: Correct demangled symbols in C++ program
PCI: cpqphp: Use PCI_POSSIBLE_ERROR() to check config reads
PCI: cpqphp: Fix PCIBIOS_* return value confusion
f2fs: fix the wrong f2fs_bug_on condition in f2fs_do_replace_block
f2fs: avoid using native allocate_segment_by_default()
f2fs: remove struct segment_allocation default_salloc_ops
f2fs: open code allocate_segment_by_default
f2fs: remove the unused flush argument to change_curseg
f2fs: check curseg->inited before write_sum_page in change_curseg
perf trace: avoid garbage when not printing a trace event's arguments
m68k: mcfgpio: Fix incorrect register offset for CONFIG_M5441x
m68k: coldfire/device.c: only build FEC when HW macros are defined
perf trace: Do not lose last events in a race
perf trace: Avoid garbage when not printing a syscall's arguments
rpmsg: glink: Add TX_DATA_CONT command while sending
rpmsg: glink: Send READ_NOTIFY command in FIFO full case
rpmsg: glink: Fix GLINK command prefix
rpmsg: glink: use only lower 16-bits of param2 for CMD_OPEN name length
remoteproc: qcom_q6v5_mss: Re-order writes to the IMEM region
NFSD: Prevent NULL dereference in nfsd4_process_cb_update()
NFSD: Cap the number of bytes copied by nfs4_reset_recoverydir()
NFSD: Fix nfsd4_shutdown_copy()
vdpa/mlx5: Fix suboptimal range on iotlb iteration
vfio/pci: Properly hide first-in-list PCIe extended capability
fs_parser: update mount_api doc to match function signature
power: supply: core: Remove might_sleep() from power_supply_put()
power: supply: bq27xxx: Support CHARGE_NOW for bq27z561/bq28z610/bq34z100
power: supply: bq27xxx: Fix registers of bq27426
net: usb: lan78xx: Fix memory leak on device unplug by freeing PHY device
tg3: Set coherent DMA mask bits to 31 for BCM57766 chipsets
net: usb: lan78xx: Fix refcounting and autosuspend on invalid WoL configuration
marvell: pxa168_eth: fix call balance of pep->clk handling routines
net: stmmac: dwmac-socfpga: Set RX watchdog interrupt as broken
spi: atmel-quadspi: Fix register name in verbose logging function
net: introduce a netdev feature for UDP GRO forwarding
net: hsr: fix hsr_init_sk() vs network/transport headers.
bnxt_en: Reserve rings after PCIe AER recovery if NIC interface is down
ipmr: convert /proc handlers to rcu_read_lock()
ipmr: fix tables suspicious RCU usage
iio: light: al3010: Fix an error handling path in al3010_probe()
usb: using mutex lock and supporting O_NONBLOCK flag in iowarrior_read()
usb: yurex: make waiting on yurex_write interruptible
USB: chaoskey: fail open after removal
USB: chaoskey: Fix possible deadlock chaoskey_list_lock
misc: apds990x: Fix missing pm_runtime_disable()
staging: greybus: uart: clean up TIOCGSERIAL
ALSA: hda/realtek - Add type for ALC287
ALSA: hda/realtek: Update ALC256 depop procedure
apparmor: fix 'Do simple duplicate message elimination'
xen: Fix the issue of resource not being properly released in xenbus_dev_probe()
usb: ehci-spear: fix call balance of sehci clk handling routines
soc: qcom: socinfo: fix revision check in qcom_socinfo_probe()
ALSA: usb-audio: Fix potential out-of-bound accesses for Extigy and Mbox devices
ext4: supress data-race warnings in ext4_free_inodes_{count,set}()
ext4: fix FS_IOC_GETFSMAP handling
jfs: xattr: check invalid xattr size more strictly
ASoC: codecs: Fix atomicity violation in snd_soc_component_get_drvdata()
perf/x86/intel/pt: Fix buffer full but size is 0 case
crypto: x86/aegis128 - access 32-bit arguments as 32-bit
KVM: arm64: Ignore PMCNTENSET_EL0 while checking for overflow status
PCI: Fix use-after-free of slot->bus on hot remove
fsnotify: fix sending inotify event with unexpected filename
comedi: Flush partial mappings in error case
apparmor: test: Fix memory leak for aa_unpack_strdup()
tty: ldsic: fix tty_ldisc_autoload sysctl's proc_handler
locking/lockdep: Avoid creating new name string literals in lockdep_set_subclass()
exfat: fix uninit-value in __exfat_get_dentry_set
Bluetooth: Fix type of len in rfcomm_sock_getsockopt{,_old}()
driver core: bus: Fix double free in driver API bus_register()
Revert "usb: gadget: composite: fix OS descriptors w_value logic"
serial: sh-sci: Clean sci_ports[0] after at earlycon exit
Revert "serial: sh-sci: Clean sci_ports[0] after at earlycon exit"
netfilter: ipset: add missing range check in bitmap_ip_uadt
spi: Fix acpi deferred irq probe
platform/chrome: cros_ec_typec: fix missing fwnode reference decrement
ubi: wl: Put source PEB into correct list if trying locking LEB failed
um: ubd: Do not use drvdata in release
um: net: Do not use drvdata in release
serial: 8250: omap: Move pm_runtime_get_sync
um: vector: Do not use drvdata in release
sh: cpuinfo: Fix a warning for CONFIG_CPUMASK_OFFSTACK
arm64: tls: Fix context-switching of tpidrro_el0 when kpti is enabled
block: fix ordering between checking BLK_MQ_S_STOPPED request adding
HID: wacom: Interpret tilt data from Intuos Pro BT as signed values
media: wl128x: Fix atomicity violation in fmc_send_cmd()
media: v4l2-core: v4l2-dv-timings: check cvt/gtf result
ALSA: hda/realtek: Update ALC225 depop procedure
ALSA: hda/realtek: Set PCBeep to default value for ALC274
ALSA: hda/realtek: Fix Internal Speaker and Mic boost of Infinix Y4 Max
ALSA: hda/realtek: Apply quirk for Medion E15433
usb: dwc3: gadget: Fix checking for number of TRBs left
usb: dwc3: gadget: Fix looping of queued SG entries
lib: string_helpers: silence snprintf() output truncation warning
NFSD: Prevent a potential integer overflow
SUNRPC: make sure cache entry active before cache_show
rpmsg: glink: Propagate TX failures in intentless mode as well
um: Fix potential integer overflow during physmem setup
um: Fix the return value of elf_core_copy_task_fpregs
um: Always dump trace for specified task in show_stack
NFSv4.0: Fix a use-after-free problem in the asynchronous open()
rtc: st-lpc: Use IRQF_NO_AUTOEN flag in request_irq()
rtc: abx80x: Fix WDT bit position of the status register
rtc: check if __rtc_read_time was successful in rtc_timer_do_work()
ubifs: Correct the total block count by deducting journal reservation
ubi: fastmap: Fix duplicate slab cache names while attaching
ubifs: authentication: Fix use-after-free in ubifs_tnc_end_commit
jffs2: fix use of uninitialized variable
block: return unsigned int from bdev_io_min
9p/xen: fix init sequence
9p/xen: fix release of IRQ
rtc: ab-eoz9: don't fail temperature reads on undervoltage notification
modpost: remove incorrect code in do_eisa_entry()
nfs: ignore SB_RDONLY when mounting nfs
SUNRPC: correct error code comment in xs_tcp_setup_socket()
SUNRPC: Convert rpc_client refcount to use refcount_t
sunrpc: remove unnecessary test in rpc_task_set_client()
SUNRPC: Replace internal use of SOCKWQ_ASYNC_NOSPACE
sunrpc: clear XPRT_SOCK_UPD_TIMEOUT when reset transport
sh: intc: Fix use-after-free bug in register_intc_controller()
ASoC: fsl_micfil: fix the naming style for mask definition
octeontx2-pf: Fix out-of-bounds read in otx2_get_fecparam()
quota: flush quota_release_work upon quota writeback
btrfs: ref-verify: fix use-after-free after invalid ref action
ad7780: fix division by zero in ad7780_write_raw()
util_macros.h: fix/rework find_closest() macros
scsi: ufs: exynos: Fix hibern8 notify callbacks
i3c: master: Fix miss free init_dyn_addr at i3c_master_put_i3c_addrs()
PCI: keystone: Add link up check to ks_pcie_other_map_bus()
dm thin: Add missing destroy_work_on_stack()
nfsd: make sure exp active before svc_export_show
nfsd: fix nfs4_openowner leak when concurrent nfsd4_open occur
btrfs: don't BUG_ON on ENOMEM from btrfs_lookup_extent_info() in walk_down_proc()
drm/etnaviv: flush shader L1 cache after user commandstream
iTCO_wdt: mask NMI_NOW bit for update_no_reboot_bit() call
watchdog: mediatek: Make sure system reset gets asserted in mtk_wdt_restart()
can: sun4i_can: sun4i_can_err(): call can_change_state() even if cf is NULL
can: sun4i_can: sun4i_can_err(): fix {rx,tx}_errors statistics
ipvs: fix UB due to uninitialized stack access in ip_vs_protocol_init()
netfilter: x_tables: fix LED ID check in led_tg_check()
ptp: Add error handling for adjfine callback in ptp_clock_adjtime
net/sched: tbf: correct backlog statistic for GSO packets
net: hsr: avoid potential out-of-bound access in fill_frame_info()
can: j1939: j1939_session_new(): fix skb reference counting
net/ipv6: release expired exception dst cached in socket
dccp: Fix memory leak in dccp_feat_change_recv
tipc: Fix use-after-free of kernel socket in cleanup_bearer().
net/qed: allow old cards not supporting "num_images" to work
igb: Fix potential invalid memory access in igb_init_module()
net: sched: fix erspan_opt settings in cls_flower
netfilter: ipset: Hold module reference while requesting a module
netfilter: nft_set_hash: skip duplicated elements pending gc run
ethtool: Fix wrong mod state in case of verbose and no_mask bitset
geneve: do not assume mac header is set in geneve_xmit_skb()
gpio: grgpio: use a helper variable to store the address of ofdev->dev
gpio: grgpio: Add NULL check in grgpio_probe
dt_bindings: rs485: Correct delay values
dt-bindings: serial: rs485: Fix rs485-rts-delay property
i3c: fix incorrect address slot lookup on 64-bit
i3c: master: Replace hard code 2 with macro I3C_ADDR_SLOT_STATUS_BITS
i3c: master: Extend address status bit to 4 and add I3C_ADDR_SLOT_EXT_DESIRED
i3c: master: Fix dynamic address leak when 'assigned-address' is present
drm/sti: Add __iomem for mixer_dbg_mxn's parameter
tcp_bpf: Fix the sk_mem_uncharge logic in tcp_bpf_sendmsg
spi: mpc52xx: Add cancel_work_sync before module remove
ocfs2: free inode when ocfs2_get_init_inode() fails
bpf: Handle BPF_EXIST and BPF_NOEXIST for LPM trie
bpf: Fix exact match conditions in trie_get_next_key()
HID: wacom: fix when get product name maybe null pointer
watchdog: rti: of: honor timeout-sec property
tracing: Fix cmp_entries_dup() to respect sort() comparison rules
arm64: ptrace: fix partial SETREGSET for NT_ARM_TAGGED_ADDR_CTRL
ALSA: usb-audio: add mixer mapping for Corsair HS80
ALSA: hda/realtek: Enable mute and micmute LED on HP ProBook 430 G8
ALSA: hda/realtek: Add support for Samsung Galaxy Book3 360 (NP730QFG)
scsi: qla2xxx: Fix NVMe and NPIV connect issue
scsi: qla2xxx: Supported speed displayed incorrectly for VPorts
scsi: qla2xxx: Fix use after free on unload
scsi: qla2xxx: Remove check req_sg_cnt should be equal to rsp_sg_cnt
nilfs2: fix potential out-of-bounds memory access in nilfs_find_entry()
bcache: revert replacing IS_ERR_OR_NULL with IS_ERR again
bpf: fix OOB devmap writes when deleting elements
dma-buf: fix dma_fence_array_signaled v4
regmap: detach regmap from dev on regmap_exit
mmc: sdhci-pci: Add DMI quirk for missing CD GPIO on Vexia Edu Atla 10 tablet
mmc: core: Further prevent card detect during shutdown
ocfs2: update seq_file index in ocfs2_dlm_seq_next
iommu/arm-smmu: Defer probe of clients after smmu device bound
s390/cpum_sf: Handle CPU hotplug remove during sampling
btrfs: avoid unnecessary device path update for the same device
kselftest/arm64: Don't leak pipe fds in pac.exec_sign_all()
kcsan: Turn report_filterlist_lock into a raw_spinlock
media: uvcvideo: Add a quirk for the Kaiweets KTI-W02 infrared camera
media: cx231xx: Add support for Dexatek USB Video Grabber 1d19:6108
drm/vc4: hvs: Set AXI panic modes for the HVS
drm: panel-orientation-quirks: Add quirk for AYA NEO 2 model
drm/mcde: Enable module autoloading
drm/radeon/r600_cs: Fix possible int overflow in r600_packet3_check()
r8169: don't apply UDP padding quirk on RTL8126A
samples/bpf: Fix a resource leak
net: fec_mpc52xx_phy: Use %pa to format resource_size_t
net: ethernet: fs_enet: Use %pa to format resource_size_t
net/sched: cbs: Fix integer overflow in cbs_set_port_rate()
af_packet: avoid erroring out after sock_init_data() in packet_create()
Bluetooth: L2CAP: do not leave dangling sk pointer on error in l2cap_sock_create()
net: af_can: do not leave a dangling sk pointer in can_create()
net: ieee802154: do not leave a dangling sk pointer in ieee802154_create()
net: inet: do not leave a dangling sk pointer in inet_create()
net: inet6: do not leave a dangling sk pointer in inet6_create()
wifi: ath5k: add PCI ID for SX76X
wifi: ath5k: add PCI ID for Arcadyan devices
drm/amdgpu: refine error handling in amdgpu_ttm_tt_pin_userptr
dma-debug: fix a possible deadlock on radix_lock
jfs: array-index-out-of-bounds fix in dtReadFirst
jfs: fix shift-out-of-bounds in dbSplit
jfs: fix array-index-out-of-bounds in jfs_readdir
jfs: add a check to prevent array-index-out-of-bounds in dbAdjTree
drm/amdgpu: skip amdgpu_device_cache_pci_state under sriov
drm/amdgpu: set the right AMDGPU sg segment limitation
wifi: ipw2x00: libipw_rx_any(): fix bad alignment
wifi: brcmfmac: Fix oops due to NULL pointer dereference in brcmf_sdiod_sglist_rw()
Bluetooth: btusb: Add RTL8852BE device 0489:e123 to device tables
ASoC: hdmi-codec: reorder channel allocation list
rocker: fix link status detection in rocker_carrier_init()
net/neighbor: clear error in case strict check is not set
netpoll: Use rcu_access_pointer() in __netpoll_setup
pinctrl: freescale: fix COMPILE_TEST error with PINCTRL_IMX_SCU
tracing: Use atomic64_inc_return() in trace_clock_counter()
scsi: hisi_sas: Add cond_resched() for no forced preemption model
leds: class: Protect brightness_show() with led_cdev->led_access mutex
scsi: st: Don't modify unknown block number in MTIOCGET
scsi: st: Add MTIOCGET and MTLOAD to ioctls allowed after device reset
pinctrl: qcom-pmic-gpio: add support for PM8937
nvdimm: rectify the illogical code within nd_dax_probe()
f2fs: fix f2fs_bug_on when uninstalling filesystem call f2fs_evict_inode.
PCI: Add 'reset_subordinate' to reset hierarchy below bridge
PCI: Add ACS quirk for Wangxun FF5xxx NICs
i3c: Use i3cdev->desc->info instead of calling i3c_device_get_info() to avoid deadlock
usb: chipidea: udc: handle USB Error Interrupt if IOC not set
MIPS: Loongson64: DTS: Really fix PCIe port nodes for ls7a
powerpc/prom_init: Fixup missing powermac #size-cells
misc: eeprom: eeprom_93cx6: Add quirk for extra read clock cycle
sched/core: Remove the unnecessary need_resched() check in nohz_csd_func()
sched/fair: Remove update of blocked load from newidle_balance
sched/fair: Remove unused parameter of update_nohz_stats
sched/fair: Merge for each idle cpu loop of ILB
sched/fair: Trigger the update of blocked load on newly idle cpu
sched/fair: Add NOHZ balancer flag for nohz.next_balance updates
sched/fair: Check idle_cpu() before need_resched() to detect ilb CPU turning busy
sched/core: Prevent wakeup of ksoftirqd during idle load balance
btrfs: fix missing snapshot drew unlock when root is dead during swap activation
Revert "unicode: Don't special case ignorable code points"
KVM: arm64: vgic-its: Add a data length check in vgic_its_save_*
KVM: arm64: vgic-its: Clear DTE when MAPD unmaps a device
KVM: arm64: vgic-its: Clear ITE when DISCARD frees an ITE
jffs2: Prevent rtime decompress memory corruption
jffs2: Fix rtime decompressor
xhci: dbc: Fix STALL transfer event handling
drm/amd/display: Check BIOS images before it is used
ocfs2: Revert "ocfs2: fix the la space leak when unmounting an ocfs2 volume"
modpost: Add .irqentry.text to OTHER_SECTIONS
Revert "drm/amdgpu: add missing size check in amdgpu_debugfs_gprwave_read()"
PCI: rockchip-ep: Fix address translation unit programming
scsi: sd: Fix sd_do_mode_sense() buffer length handling
scsi: core: Fix scsi_mode_select() buffer length handling
ALSA: usb-audio: Fix out of bounds reads when finding clock sources
media: uvcvideo: Require entities to have a non-zero unique ID
octeontx2: Fix condition.
octeontx2-pf: Fix otx2_get_fecparam()
Linux 5.10.231
Change-Id: I9f898f01cfaab475581c068959ac597789122af0
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
1619 lines
42 KiB
C
1619 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2008 Advanced Micro Devices, Inc.
|
|
*
|
|
* Author: Joerg Roedel <joerg.roedel@amd.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "DMA-API: " fmt
|
|
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/dma-map-ops.h>
|
|
#include <linux/sched/task.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/export.h>
|
|
#include <linux/device.h>
|
|
#include <linux/types.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <asm/sections.h>
|
|
#include "debug.h"
|
|
|
|
#define HASH_SIZE 16384ULL
|
|
#define HASH_FN_SHIFT 13
|
|
#define HASH_FN_MASK (HASH_SIZE - 1)
|
|
|
|
#define PREALLOC_DMA_DEBUG_ENTRIES (1 << 16)
|
|
/* If the pool runs out, add this many new entries at once */
|
|
#define DMA_DEBUG_DYNAMIC_ENTRIES (PAGE_SIZE / sizeof(struct dma_debug_entry))
|
|
|
|
enum {
|
|
dma_debug_single,
|
|
dma_debug_sg,
|
|
dma_debug_coherent,
|
|
dma_debug_resource,
|
|
};
|
|
|
|
enum map_err_types {
|
|
MAP_ERR_CHECK_NOT_APPLICABLE,
|
|
MAP_ERR_NOT_CHECKED,
|
|
MAP_ERR_CHECKED,
|
|
};
|
|
|
|
#define DMA_DEBUG_STACKTRACE_ENTRIES 5
|
|
|
|
/**
|
|
* struct dma_debug_entry - track a dma_map* or dma_alloc_coherent mapping
|
|
* @list: node on pre-allocated free_entries list
|
|
* @dev: 'dev' argument to dma_map_{page|single|sg} or dma_alloc_coherent
|
|
* @size: length of the mapping
|
|
* @type: single, page, sg, coherent
|
|
* @direction: enum dma_data_direction
|
|
* @sg_call_ents: 'nents' from dma_map_sg
|
|
* @sg_mapped_ents: 'mapped_ents' from dma_map_sg
|
|
* @pfn: page frame of the start address
|
|
* @offset: offset of mapping relative to pfn
|
|
* @map_err_type: track whether dma_mapping_error() was checked
|
|
* @stacktrace: support backtraces when a violation is detected
|
|
*/
|
|
struct dma_debug_entry {
|
|
struct list_head list;
|
|
struct device *dev;
|
|
u64 dev_addr;
|
|
u64 size;
|
|
int type;
|
|
int direction;
|
|
int sg_call_ents;
|
|
int sg_mapped_ents;
|
|
unsigned long pfn;
|
|
size_t offset;
|
|
enum map_err_types map_err_type;
|
|
#ifdef CONFIG_STACKTRACE
|
|
unsigned int stack_len;
|
|
unsigned long stack_entries[DMA_DEBUG_STACKTRACE_ENTRIES];
|
|
#endif
|
|
} ____cacheline_aligned_in_smp;
|
|
|
|
typedef bool (*match_fn)(struct dma_debug_entry *, struct dma_debug_entry *);
|
|
|
|
struct hash_bucket {
|
|
struct list_head list;
|
|
spinlock_t lock;
|
|
};
|
|
|
|
/* Hash list to save the allocated dma addresses */
|
|
static struct hash_bucket dma_entry_hash[HASH_SIZE];
|
|
/* List of pre-allocated dma_debug_entry's */
|
|
static LIST_HEAD(free_entries);
|
|
/* Lock for the list above */
|
|
static DEFINE_SPINLOCK(free_entries_lock);
|
|
|
|
/* Global disable flag - will be set in case of an error */
|
|
static bool global_disable __read_mostly;
|
|
|
|
/* Early initialization disable flag, set at the end of dma_debug_init */
|
|
static bool dma_debug_initialized __read_mostly;
|
|
|
|
static inline bool dma_debug_disabled(void)
|
|
{
|
|
return global_disable || !dma_debug_initialized;
|
|
}
|
|
|
|
/* Global error count */
|
|
static u32 error_count;
|
|
|
|
/* Global error show enable*/
|
|
static u32 show_all_errors __read_mostly;
|
|
/* Number of errors to show */
|
|
static u32 show_num_errors = 1;
|
|
|
|
static u32 num_free_entries;
|
|
static u32 min_free_entries;
|
|
static u32 nr_total_entries;
|
|
|
|
/* number of preallocated entries requested by kernel cmdline */
|
|
static u32 nr_prealloc_entries = PREALLOC_DMA_DEBUG_ENTRIES;
|
|
|
|
/* per-driver filter related state */
|
|
|
|
#define NAME_MAX_LEN 64
|
|
|
|
static char current_driver_name[NAME_MAX_LEN] __read_mostly;
|
|
static struct device_driver *current_driver __read_mostly;
|
|
|
|
static DEFINE_RWLOCK(driver_name_lock);
|
|
|
|
static const char *const maperr2str[] = {
|
|
[MAP_ERR_CHECK_NOT_APPLICABLE] = "dma map error check not applicable",
|
|
[MAP_ERR_NOT_CHECKED] = "dma map error not checked",
|
|
[MAP_ERR_CHECKED] = "dma map error checked",
|
|
};
|
|
|
|
static const char *type2name[] = {
|
|
[dma_debug_single] = "single",
|
|
[dma_debug_sg] = "scather-gather",
|
|
[dma_debug_coherent] = "coherent",
|
|
[dma_debug_resource] = "resource",
|
|
};
|
|
|
|
static const char *dir2name[] = {
|
|
[DMA_BIDIRECTIONAL] = "DMA_BIDIRECTIONAL",
|
|
[DMA_TO_DEVICE] = "DMA_TO_DEVICE",
|
|
[DMA_FROM_DEVICE] = "DMA_FROM_DEVICE",
|
|
[DMA_NONE] = "DMA_NONE",
|
|
};
|
|
|
|
/*
|
|
* The access to some variables in this macro is racy. We can't use atomic_t
|
|
* here because all these variables are exported to debugfs. Some of them even
|
|
* writeable. This is also the reason why a lock won't help much. But anyway,
|
|
* the races are no big deal. Here is why:
|
|
*
|
|
* error_count: the addition is racy, but the worst thing that can happen is
|
|
* that we don't count some errors
|
|
* show_num_errors: the subtraction is racy. Also no big deal because in
|
|
* worst case this will result in one warning more in the
|
|
* system log than the user configured. This variable is
|
|
* writeable via debugfs.
|
|
*/
|
|
static inline void dump_entry_trace(struct dma_debug_entry *entry)
|
|
{
|
|
#ifdef CONFIG_STACKTRACE
|
|
if (entry) {
|
|
pr_warn("Mapped at:\n");
|
|
stack_trace_print(entry->stack_entries, entry->stack_len, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static bool driver_filter(struct device *dev)
|
|
{
|
|
struct device_driver *drv;
|
|
unsigned long flags;
|
|
bool ret;
|
|
|
|
/* driver filter off */
|
|
if (likely(!current_driver_name[0]))
|
|
return true;
|
|
|
|
/* driver filter on and initialized */
|
|
if (current_driver && dev && dev->driver == current_driver)
|
|
return true;
|
|
|
|
/* driver filter on, but we can't filter on a NULL device... */
|
|
if (!dev)
|
|
return false;
|
|
|
|
if (current_driver || !current_driver_name[0])
|
|
return false;
|
|
|
|
/* driver filter on but not yet initialized */
|
|
drv = dev->driver;
|
|
if (!drv)
|
|
return false;
|
|
|
|
/* lock to protect against change of current_driver_name */
|
|
read_lock_irqsave(&driver_name_lock, flags);
|
|
|
|
ret = false;
|
|
if (drv->name &&
|
|
strncmp(current_driver_name, drv->name, NAME_MAX_LEN - 1) == 0) {
|
|
current_driver = drv;
|
|
ret = true;
|
|
}
|
|
|
|
read_unlock_irqrestore(&driver_name_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define err_printk(dev, entry, format, arg...) do { \
|
|
error_count += 1; \
|
|
if (driver_filter(dev) && \
|
|
(show_all_errors || show_num_errors > 0)) { \
|
|
WARN(1, pr_fmt("%s %s: ") format, \
|
|
dev ? dev_driver_string(dev) : "NULL", \
|
|
dev ? dev_name(dev) : "NULL", ## arg); \
|
|
dump_entry_trace(entry); \
|
|
} \
|
|
if (!show_all_errors && show_num_errors > 0) \
|
|
show_num_errors -= 1; \
|
|
} while (0);
|
|
|
|
/*
|
|
* Hash related functions
|
|
*
|
|
* Every DMA-API request is saved into a struct dma_debug_entry. To
|
|
* have quick access to these structs they are stored into a hash.
|
|
*/
|
|
static int hash_fn(struct dma_debug_entry *entry)
|
|
{
|
|
/*
|
|
* Hash function is based on the dma address.
|
|
* We use bits 20-27 here as the index into the hash
|
|
*/
|
|
return (entry->dev_addr >> HASH_FN_SHIFT) & HASH_FN_MASK;
|
|
}
|
|
|
|
/*
|
|
* Request exclusive access to a hash bucket for a given dma_debug_entry.
|
|
*/
|
|
static struct hash_bucket *get_hash_bucket(struct dma_debug_entry *entry,
|
|
unsigned long *flags)
|
|
__acquires(&dma_entry_hash[idx].lock)
|
|
{
|
|
int idx = hash_fn(entry);
|
|
unsigned long __flags;
|
|
|
|
spin_lock_irqsave(&dma_entry_hash[idx].lock, __flags);
|
|
*flags = __flags;
|
|
return &dma_entry_hash[idx];
|
|
}
|
|
|
|
/*
|
|
* Give up exclusive access to the hash bucket
|
|
*/
|
|
static void put_hash_bucket(struct hash_bucket *bucket,
|
|
unsigned long flags)
|
|
__releases(&bucket->lock)
|
|
{
|
|
spin_unlock_irqrestore(&bucket->lock, flags);
|
|
}
|
|
|
|
static bool exact_match(struct dma_debug_entry *a, struct dma_debug_entry *b)
|
|
{
|
|
return ((a->dev_addr == b->dev_addr) &&
|
|
(a->dev == b->dev)) ? true : false;
|
|
}
|
|
|
|
static bool containing_match(struct dma_debug_entry *a,
|
|
struct dma_debug_entry *b)
|
|
{
|
|
if (a->dev != b->dev)
|
|
return false;
|
|
|
|
if ((b->dev_addr <= a->dev_addr) &&
|
|
((b->dev_addr + b->size) >= (a->dev_addr + a->size)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Search a given entry in the hash bucket list
|
|
*/
|
|
static struct dma_debug_entry *__hash_bucket_find(struct hash_bucket *bucket,
|
|
struct dma_debug_entry *ref,
|
|
match_fn match)
|
|
{
|
|
struct dma_debug_entry *entry, *ret = NULL;
|
|
int matches = 0, match_lvl, last_lvl = -1;
|
|
|
|
list_for_each_entry(entry, &bucket->list, list) {
|
|
if (!match(ref, entry))
|
|
continue;
|
|
|
|
/*
|
|
* Some drivers map the same physical address multiple
|
|
* times. Without a hardware IOMMU this results in the
|
|
* same device addresses being put into the dma-debug
|
|
* hash multiple times too. This can result in false
|
|
* positives being reported. Therefore we implement a
|
|
* best-fit algorithm here which returns the entry from
|
|
* the hash which fits best to the reference value
|
|
* instead of the first-fit.
|
|
*/
|
|
matches += 1;
|
|
match_lvl = 0;
|
|
entry->size == ref->size ? ++match_lvl : 0;
|
|
entry->type == ref->type ? ++match_lvl : 0;
|
|
entry->direction == ref->direction ? ++match_lvl : 0;
|
|
entry->sg_call_ents == ref->sg_call_ents ? ++match_lvl : 0;
|
|
|
|
if (match_lvl == 4) {
|
|
/* perfect-fit - return the result */
|
|
return entry;
|
|
} else if (match_lvl > last_lvl) {
|
|
/*
|
|
* We found an entry that fits better then the
|
|
* previous one or it is the 1st match.
|
|
*/
|
|
last_lvl = match_lvl;
|
|
ret = entry;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we have multiple matches but no perfect-fit, just return
|
|
* NULL.
|
|
*/
|
|
ret = (matches == 1) ? ret : NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct dma_debug_entry *bucket_find_exact(struct hash_bucket *bucket,
|
|
struct dma_debug_entry *ref)
|
|
{
|
|
return __hash_bucket_find(bucket, ref, exact_match);
|
|
}
|
|
|
|
static struct dma_debug_entry *bucket_find_contain(struct hash_bucket **bucket,
|
|
struct dma_debug_entry *ref,
|
|
unsigned long *flags)
|
|
{
|
|
|
|
unsigned int max_range = dma_get_max_seg_size(ref->dev);
|
|
struct dma_debug_entry *entry, index = *ref;
|
|
unsigned int range = 0;
|
|
|
|
while (range <= max_range) {
|
|
entry = __hash_bucket_find(*bucket, ref, containing_match);
|
|
|
|
if (entry)
|
|
return entry;
|
|
|
|
/*
|
|
* Nothing found, go back a hash bucket
|
|
*/
|
|
put_hash_bucket(*bucket, *flags);
|
|
range += (1 << HASH_FN_SHIFT);
|
|
index.dev_addr -= (1 << HASH_FN_SHIFT);
|
|
*bucket = get_hash_bucket(&index, flags);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Add an entry to a hash bucket
|
|
*/
|
|
static void hash_bucket_add(struct hash_bucket *bucket,
|
|
struct dma_debug_entry *entry)
|
|
{
|
|
list_add_tail(&entry->list, &bucket->list);
|
|
}
|
|
|
|
/*
|
|
* Remove entry from a hash bucket list
|
|
*/
|
|
static void hash_bucket_del(struct dma_debug_entry *entry)
|
|
{
|
|
list_del(&entry->list);
|
|
}
|
|
|
|
static unsigned long long phys_addr(struct dma_debug_entry *entry)
|
|
{
|
|
if (entry->type == dma_debug_resource)
|
|
return __pfn_to_phys(entry->pfn) + entry->offset;
|
|
|
|
return page_to_phys(pfn_to_page(entry->pfn)) + entry->offset;
|
|
}
|
|
|
|
/*
|
|
* Dump mapping entries for debugging purposes
|
|
*/
|
|
void debug_dma_dump_mappings(struct device *dev)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < HASH_SIZE; idx++) {
|
|
struct hash_bucket *bucket = &dma_entry_hash[idx];
|
|
struct dma_debug_entry *entry;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bucket->lock, flags);
|
|
|
|
list_for_each_entry(entry, &bucket->list, list) {
|
|
if (!dev || dev == entry->dev) {
|
|
dev_info(entry->dev,
|
|
"%s idx %d P=%Lx N=%lx D=%Lx L=%Lx %s %s\n",
|
|
type2name[entry->type], idx,
|
|
phys_addr(entry), entry->pfn,
|
|
entry->dev_addr, entry->size,
|
|
dir2name[entry->direction],
|
|
maperr2str[entry->map_err_type]);
|
|
}
|
|
}
|
|
|
|
spin_unlock_irqrestore(&bucket->lock, flags);
|
|
cond_resched();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For each mapping (initial cacheline in the case of
|
|
* dma_alloc_coherent/dma_map_page, initial cacheline in each page of a
|
|
* scatterlist, or the cacheline specified in dma_map_single) insert
|
|
* into this tree using the cacheline as the key. At
|
|
* dma_unmap_{single|sg|page} or dma_free_coherent delete the entry. If
|
|
* the entry already exists at insertion time add a tag as a reference
|
|
* count for the overlapping mappings. For now, the overlap tracking
|
|
* just ensures that 'unmaps' balance 'maps' before marking the
|
|
* cacheline idle, but we should also be flagging overlaps as an API
|
|
* violation.
|
|
*
|
|
* Memory usage is mostly constrained by the maximum number of available
|
|
* dma-debug entries in that we need a free dma_debug_entry before
|
|
* inserting into the tree. In the case of dma_map_page and
|
|
* dma_alloc_coherent there is only one dma_debug_entry and one
|
|
* dma_active_cacheline entry to track per event. dma_map_sg(), on the
|
|
* other hand, consumes a single dma_debug_entry, but inserts 'nents'
|
|
* entries into the tree.
|
|
*
|
|
* Use __GFP_NOWARN because the printk from an OOM, to netconsole, could end
|
|
* up right back in the DMA debugging code, leading to a deadlock.
|
|
*/
|
|
static RADIX_TREE(dma_active_cacheline, GFP_ATOMIC | __GFP_NOWARN);
|
|
static DEFINE_SPINLOCK(radix_lock);
|
|
#define ACTIVE_CACHELINE_MAX_OVERLAP ((1 << RADIX_TREE_MAX_TAGS) - 1)
|
|
#define CACHELINE_PER_PAGE_SHIFT (PAGE_SHIFT - L1_CACHE_SHIFT)
|
|
#define CACHELINES_PER_PAGE (1 << CACHELINE_PER_PAGE_SHIFT)
|
|
|
|
static phys_addr_t to_cacheline_number(struct dma_debug_entry *entry)
|
|
{
|
|
return (entry->pfn << CACHELINE_PER_PAGE_SHIFT) +
|
|
(entry->offset >> L1_CACHE_SHIFT);
|
|
}
|
|
|
|
static int active_cacheline_read_overlap(phys_addr_t cln)
|
|
{
|
|
int overlap = 0, i;
|
|
|
|
for (i = RADIX_TREE_MAX_TAGS - 1; i >= 0; i--)
|
|
if (radix_tree_tag_get(&dma_active_cacheline, cln, i))
|
|
overlap |= 1 << i;
|
|
return overlap;
|
|
}
|
|
|
|
static int active_cacheline_set_overlap(phys_addr_t cln, int overlap)
|
|
{
|
|
int i;
|
|
|
|
if (overlap > ACTIVE_CACHELINE_MAX_OVERLAP || overlap < 0)
|
|
return overlap;
|
|
|
|
for (i = RADIX_TREE_MAX_TAGS - 1; i >= 0; i--)
|
|
if (overlap & 1 << i)
|
|
radix_tree_tag_set(&dma_active_cacheline, cln, i);
|
|
else
|
|
radix_tree_tag_clear(&dma_active_cacheline, cln, i);
|
|
|
|
return overlap;
|
|
}
|
|
|
|
static void active_cacheline_inc_overlap(phys_addr_t cln)
|
|
{
|
|
int overlap = active_cacheline_read_overlap(cln);
|
|
|
|
overlap = active_cacheline_set_overlap(cln, ++overlap);
|
|
|
|
/* If we overflowed the overlap counter then we're potentially
|
|
* leaking dma-mappings.
|
|
*/
|
|
WARN_ONCE(overlap > ACTIVE_CACHELINE_MAX_OVERLAP,
|
|
pr_fmt("exceeded %d overlapping mappings of cacheline %pa\n"),
|
|
ACTIVE_CACHELINE_MAX_OVERLAP, &cln);
|
|
}
|
|
|
|
static int active_cacheline_dec_overlap(phys_addr_t cln)
|
|
{
|
|
int overlap = active_cacheline_read_overlap(cln);
|
|
|
|
return active_cacheline_set_overlap(cln, --overlap);
|
|
}
|
|
|
|
static int active_cacheline_insert(struct dma_debug_entry *entry)
|
|
{
|
|
phys_addr_t cln = to_cacheline_number(entry);
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
/* If the device is not writing memory then we don't have any
|
|
* concerns about the cpu consuming stale data. This mitigates
|
|
* legitimate usages of overlapping mappings.
|
|
*/
|
|
if (entry->direction == DMA_TO_DEVICE)
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&radix_lock, flags);
|
|
rc = radix_tree_insert(&dma_active_cacheline, cln, entry);
|
|
if (rc == -EEXIST)
|
|
active_cacheline_inc_overlap(cln);
|
|
spin_unlock_irqrestore(&radix_lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void active_cacheline_remove(struct dma_debug_entry *entry)
|
|
{
|
|
phys_addr_t cln = to_cacheline_number(entry);
|
|
unsigned long flags;
|
|
|
|
/* ...mirror the insert case */
|
|
if (entry->direction == DMA_TO_DEVICE)
|
|
return;
|
|
|
|
spin_lock_irqsave(&radix_lock, flags);
|
|
/* since we are counting overlaps the final put of the
|
|
* cacheline will occur when the overlap count is 0.
|
|
* active_cacheline_dec_overlap() returns -1 in that case
|
|
*/
|
|
if (active_cacheline_dec_overlap(cln) < 0)
|
|
radix_tree_delete(&dma_active_cacheline, cln);
|
|
spin_unlock_irqrestore(&radix_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Wrapper function for adding an entry to the hash.
|
|
* This function takes care of locking itself.
|
|
*/
|
|
static void add_dma_entry(struct dma_debug_entry *entry)
|
|
{
|
|
struct hash_bucket *bucket;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
bucket = get_hash_bucket(entry, &flags);
|
|
hash_bucket_add(bucket, entry);
|
|
put_hash_bucket(bucket, flags);
|
|
|
|
rc = active_cacheline_insert(entry);
|
|
if (rc == -ENOMEM) {
|
|
pr_err_once("cacheline tracking ENOMEM, dma-debug disabled\n");
|
|
global_disable = true;
|
|
}
|
|
|
|
/* TODO: report -EEXIST errors here as overlapping mappings are
|
|
* not supported by the DMA API
|
|
*/
|
|
}
|
|
|
|
static int dma_debug_create_entries(gfp_t gfp)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
int i;
|
|
|
|
entry = (void *)get_zeroed_page(gfp);
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < DMA_DEBUG_DYNAMIC_ENTRIES; i++)
|
|
list_add_tail(&entry[i].list, &free_entries);
|
|
|
|
num_free_entries += DMA_DEBUG_DYNAMIC_ENTRIES;
|
|
nr_total_entries += DMA_DEBUG_DYNAMIC_ENTRIES;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct dma_debug_entry *__dma_entry_alloc(void)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
|
|
entry = list_entry(free_entries.next, struct dma_debug_entry, list);
|
|
list_del(&entry->list);
|
|
memset(entry, 0, sizeof(*entry));
|
|
|
|
num_free_entries -= 1;
|
|
if (num_free_entries < min_free_entries)
|
|
min_free_entries = num_free_entries;
|
|
|
|
return entry;
|
|
}
|
|
|
|
/*
|
|
* This should be called outside of free_entries_lock scope to avoid potential
|
|
* deadlocks with serial consoles that use DMA.
|
|
*/
|
|
static void __dma_entry_alloc_check_leak(u32 nr_entries)
|
|
{
|
|
u32 tmp = nr_entries % nr_prealloc_entries;
|
|
|
|
/* Shout each time we tick over some multiple of the initial pool */
|
|
if (tmp < DMA_DEBUG_DYNAMIC_ENTRIES) {
|
|
pr_info("dma_debug_entry pool grown to %u (%u00%%)\n",
|
|
nr_entries,
|
|
(nr_entries / nr_prealloc_entries));
|
|
}
|
|
}
|
|
|
|
/* struct dma_entry allocator
|
|
*
|
|
* The next two functions implement the allocator for
|
|
* struct dma_debug_entries.
|
|
*/
|
|
static struct dma_debug_entry *dma_entry_alloc(void)
|
|
{
|
|
bool alloc_check_leak = false;
|
|
struct dma_debug_entry *entry;
|
|
unsigned long flags;
|
|
u32 nr_entries;
|
|
|
|
spin_lock_irqsave(&free_entries_lock, flags);
|
|
if (num_free_entries == 0) {
|
|
if (dma_debug_create_entries(GFP_ATOMIC)) {
|
|
global_disable = true;
|
|
spin_unlock_irqrestore(&free_entries_lock, flags);
|
|
pr_err("debugging out of memory - disabling\n");
|
|
return NULL;
|
|
}
|
|
alloc_check_leak = true;
|
|
nr_entries = nr_total_entries;
|
|
}
|
|
|
|
entry = __dma_entry_alloc();
|
|
|
|
spin_unlock_irqrestore(&free_entries_lock, flags);
|
|
|
|
if (alloc_check_leak)
|
|
__dma_entry_alloc_check_leak(nr_entries);
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
entry->stack_len = stack_trace_save(entry->stack_entries,
|
|
ARRAY_SIZE(entry->stack_entries),
|
|
1);
|
|
#endif
|
|
return entry;
|
|
}
|
|
|
|
static void dma_entry_free(struct dma_debug_entry *entry)
|
|
{
|
|
unsigned long flags;
|
|
|
|
active_cacheline_remove(entry);
|
|
|
|
/*
|
|
* add to beginning of the list - this way the entries are
|
|
* more likely cache hot when they are reallocated.
|
|
*/
|
|
spin_lock_irqsave(&free_entries_lock, flags);
|
|
list_add(&entry->list, &free_entries);
|
|
num_free_entries += 1;
|
|
spin_unlock_irqrestore(&free_entries_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* DMA-API debugging init code
|
|
*
|
|
* The init code does two things:
|
|
* 1. Initialize core data structures
|
|
* 2. Preallocate a given number of dma_debug_entry structs
|
|
*/
|
|
|
|
static ssize_t filter_read(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[NAME_MAX_LEN + 1];
|
|
unsigned long flags;
|
|
int len;
|
|
|
|
if (!current_driver_name[0])
|
|
return 0;
|
|
|
|
/*
|
|
* We can't copy to userspace directly because current_driver_name can
|
|
* only be read under the driver_name_lock with irqs disabled. So
|
|
* create a temporary copy first.
|
|
*/
|
|
read_lock_irqsave(&driver_name_lock, flags);
|
|
len = scnprintf(buf, NAME_MAX_LEN + 1, "%s\n", current_driver_name);
|
|
read_unlock_irqrestore(&driver_name_lock, flags);
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
}
|
|
|
|
static ssize_t filter_write(struct file *file, const char __user *userbuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[NAME_MAX_LEN];
|
|
unsigned long flags;
|
|
size_t len;
|
|
int i;
|
|
|
|
/*
|
|
* We can't copy from userspace directly. Access to
|
|
* current_driver_name is protected with a write_lock with irqs
|
|
* disabled. Since copy_from_user can fault and may sleep we
|
|
* need to copy to temporary buffer first
|
|
*/
|
|
len = min(count, (size_t)(NAME_MAX_LEN - 1));
|
|
if (copy_from_user(buf, userbuf, len))
|
|
return -EFAULT;
|
|
|
|
buf[len] = 0;
|
|
|
|
write_lock_irqsave(&driver_name_lock, flags);
|
|
|
|
/*
|
|
* Now handle the string we got from userspace very carefully.
|
|
* The rules are:
|
|
* - only use the first token we got
|
|
* - token delimiter is everything looking like a space
|
|
* character (' ', '\n', '\t' ...)
|
|
*
|
|
*/
|
|
if (!isalnum(buf[0])) {
|
|
/*
|
|
* If the first character userspace gave us is not
|
|
* alphanumerical then assume the filter should be
|
|
* switched off.
|
|
*/
|
|
if (current_driver_name[0])
|
|
pr_info("switching off dma-debug driver filter\n");
|
|
current_driver_name[0] = 0;
|
|
current_driver = NULL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
/*
|
|
* Now parse out the first token and use it as the name for the
|
|
* driver to filter for.
|
|
*/
|
|
for (i = 0; i < NAME_MAX_LEN - 1; ++i) {
|
|
current_driver_name[i] = buf[i];
|
|
if (isspace(buf[i]) || buf[i] == ' ' || buf[i] == 0)
|
|
break;
|
|
}
|
|
current_driver_name[i] = 0;
|
|
current_driver = NULL;
|
|
|
|
pr_info("enable driver filter for driver [%s]\n",
|
|
current_driver_name);
|
|
|
|
out_unlock:
|
|
write_unlock_irqrestore(&driver_name_lock, flags);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations filter_fops = {
|
|
.read = filter_read,
|
|
.write = filter_write,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static int dump_show(struct seq_file *seq, void *v)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < HASH_SIZE; idx++) {
|
|
struct hash_bucket *bucket = &dma_entry_hash[idx];
|
|
struct dma_debug_entry *entry;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&bucket->lock, flags);
|
|
list_for_each_entry(entry, &bucket->list, list) {
|
|
seq_printf(seq,
|
|
"%s %s %s idx %d P=%llx N=%lx D=%llx L=%llx %s %s\n",
|
|
dev_name(entry->dev),
|
|
dev_driver_string(entry->dev),
|
|
type2name[entry->type], idx,
|
|
phys_addr(entry), entry->pfn,
|
|
entry->dev_addr, entry->size,
|
|
dir2name[entry->direction],
|
|
maperr2str[entry->map_err_type]);
|
|
}
|
|
spin_unlock_irqrestore(&bucket->lock, flags);
|
|
}
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(dump);
|
|
|
|
static int __init dma_debug_fs_init(void)
|
|
{
|
|
struct dentry *dentry = debugfs_create_dir("dma-api", NULL);
|
|
|
|
debugfs_create_bool("disabled", 0444, dentry, &global_disable);
|
|
debugfs_create_u32("error_count", 0444, dentry, &error_count);
|
|
debugfs_create_u32("all_errors", 0644, dentry, &show_all_errors);
|
|
debugfs_create_u32("num_errors", 0644, dentry, &show_num_errors);
|
|
debugfs_create_u32("num_free_entries", 0444, dentry, &num_free_entries);
|
|
debugfs_create_u32("min_free_entries", 0444, dentry, &min_free_entries);
|
|
debugfs_create_u32("nr_total_entries", 0444, dentry, &nr_total_entries);
|
|
debugfs_create_file("driver_filter", 0644, dentry, NULL, &filter_fops);
|
|
debugfs_create_file("dump", 0444, dentry, NULL, &dump_fops);
|
|
|
|
return 0;
|
|
}
|
|
core_initcall_sync(dma_debug_fs_init);
|
|
|
|
static int device_dma_allocations(struct device *dev, struct dma_debug_entry **out_entry)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
unsigned long flags;
|
|
int count = 0, i;
|
|
|
|
for (i = 0; i < HASH_SIZE; ++i) {
|
|
spin_lock_irqsave(&dma_entry_hash[i].lock, flags);
|
|
list_for_each_entry(entry, &dma_entry_hash[i].list, list) {
|
|
if (entry->dev == dev) {
|
|
count += 1;
|
|
*out_entry = entry;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&dma_entry_hash[i].lock, flags);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static int dma_debug_device_change(struct notifier_block *nb, unsigned long action, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
struct dma_debug_entry *entry;
|
|
int count;
|
|
|
|
if (dma_debug_disabled())
|
|
return 0;
|
|
|
|
switch (action) {
|
|
case BUS_NOTIFY_UNBOUND_DRIVER:
|
|
count = device_dma_allocations(dev, &entry);
|
|
if (count == 0)
|
|
break;
|
|
err_printk(dev, entry, "device driver has pending "
|
|
"DMA allocations while released from device "
|
|
"[count=%d]\n"
|
|
"One of leaked entries details: "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped with %s] [mapped as %s]\n",
|
|
count, entry->dev_addr, entry->size,
|
|
dir2name[entry->direction], type2name[entry->type]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dma_debug_add_bus(struct bus_type *bus)
|
|
{
|
|
struct notifier_block *nb;
|
|
|
|
if (dma_debug_disabled())
|
|
return;
|
|
|
|
nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);
|
|
if (nb == NULL) {
|
|
pr_err("dma_debug_add_bus: out of memory\n");
|
|
return;
|
|
}
|
|
|
|
nb->notifier_call = dma_debug_device_change;
|
|
|
|
bus_register_notifier(bus, nb);
|
|
}
|
|
|
|
static int dma_debug_init(void)
|
|
{
|
|
int i, nr_pages;
|
|
|
|
/* Do not use dma_debug_initialized here, since we really want to be
|
|
* called to set dma_debug_initialized
|
|
*/
|
|
if (global_disable)
|
|
return 0;
|
|
|
|
for (i = 0; i < HASH_SIZE; ++i) {
|
|
INIT_LIST_HEAD(&dma_entry_hash[i].list);
|
|
spin_lock_init(&dma_entry_hash[i].lock);
|
|
}
|
|
|
|
nr_pages = DIV_ROUND_UP(nr_prealloc_entries, DMA_DEBUG_DYNAMIC_ENTRIES);
|
|
for (i = 0; i < nr_pages; ++i)
|
|
dma_debug_create_entries(GFP_KERNEL);
|
|
if (num_free_entries >= nr_prealloc_entries) {
|
|
pr_info("preallocated %d debug entries\n", nr_total_entries);
|
|
} else if (num_free_entries > 0) {
|
|
pr_warn("%d debug entries requested but only %d allocated\n",
|
|
nr_prealloc_entries, nr_total_entries);
|
|
} else {
|
|
pr_err("debugging out of memory error - disabled\n");
|
|
global_disable = true;
|
|
|
|
return 0;
|
|
}
|
|
min_free_entries = num_free_entries;
|
|
|
|
dma_debug_initialized = true;
|
|
|
|
pr_info("debugging enabled by kernel config\n");
|
|
return 0;
|
|
}
|
|
core_initcall(dma_debug_init);
|
|
|
|
static __init int dma_debug_cmdline(char *str)
|
|
{
|
|
if (!str)
|
|
return -EINVAL;
|
|
|
|
if (strncmp(str, "off", 3) == 0) {
|
|
pr_info("debugging disabled on kernel command line\n");
|
|
global_disable = true;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static __init int dma_debug_entries_cmdline(char *str)
|
|
{
|
|
if (!str)
|
|
return -EINVAL;
|
|
if (!get_option(&str, &nr_prealloc_entries))
|
|
nr_prealloc_entries = PREALLOC_DMA_DEBUG_ENTRIES;
|
|
return 1;
|
|
}
|
|
|
|
__setup("dma_debug=", dma_debug_cmdline);
|
|
__setup("dma_debug_entries=", dma_debug_entries_cmdline);
|
|
|
|
static void check_unmap(struct dma_debug_entry *ref)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
struct hash_bucket *bucket;
|
|
unsigned long flags;
|
|
|
|
bucket = get_hash_bucket(ref, &flags);
|
|
entry = bucket_find_exact(bucket, ref);
|
|
|
|
if (!entry) {
|
|
/* must drop lock before calling dma_mapping_error */
|
|
put_hash_bucket(bucket, flags);
|
|
|
|
if (dma_mapping_error(ref->dev, ref->dev_addr)) {
|
|
err_printk(ref->dev, NULL,
|
|
"device driver tries to free an "
|
|
"invalid DMA memory address\n");
|
|
} else {
|
|
err_printk(ref->dev, NULL,
|
|
"device driver tries to free DMA "
|
|
"memory it has not allocated [device "
|
|
"address=0x%016llx] [size=%llu bytes]\n",
|
|
ref->dev_addr, ref->size);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ref->size != entry->size) {
|
|
err_printk(ref->dev, entry, "device driver frees "
|
|
"DMA memory with different size "
|
|
"[device address=0x%016llx] [map size=%llu bytes] "
|
|
"[unmap size=%llu bytes]\n",
|
|
ref->dev_addr, entry->size, ref->size);
|
|
}
|
|
|
|
if (ref->type != entry->type) {
|
|
err_printk(ref->dev, entry, "device driver frees "
|
|
"DMA memory with wrong function "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped as %s] [unmapped as %s]\n",
|
|
ref->dev_addr, ref->size,
|
|
type2name[entry->type], type2name[ref->type]);
|
|
} else if ((entry->type == dma_debug_coherent) &&
|
|
(phys_addr(ref) != phys_addr(entry))) {
|
|
err_printk(ref->dev, entry, "device driver frees "
|
|
"DMA memory with different CPU address "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[cpu alloc address=0x%016llx] "
|
|
"[cpu free address=0x%016llx]",
|
|
ref->dev_addr, ref->size,
|
|
phys_addr(entry),
|
|
phys_addr(ref));
|
|
}
|
|
|
|
if (ref->sg_call_ents && ref->type == dma_debug_sg &&
|
|
ref->sg_call_ents != entry->sg_call_ents) {
|
|
err_printk(ref->dev, entry, "device driver frees "
|
|
"DMA sg list with different entry count "
|
|
"[map count=%d] [unmap count=%d]\n",
|
|
entry->sg_call_ents, ref->sg_call_ents);
|
|
}
|
|
|
|
/*
|
|
* This may be no bug in reality - but most implementations of the
|
|
* DMA API don't handle this properly, so check for it here
|
|
*/
|
|
if (ref->direction != entry->direction) {
|
|
err_printk(ref->dev, entry, "device driver frees "
|
|
"DMA memory with different direction "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped with %s] [unmapped with %s]\n",
|
|
ref->dev_addr, ref->size,
|
|
dir2name[entry->direction],
|
|
dir2name[ref->direction]);
|
|
}
|
|
|
|
/*
|
|
* Drivers should use dma_mapping_error() to check the returned
|
|
* addresses of dma_map_single() and dma_map_page().
|
|
* If not, print this warning message. See Documentation/core-api/dma-api.rst.
|
|
*/
|
|
if (entry->map_err_type == MAP_ERR_NOT_CHECKED) {
|
|
err_printk(ref->dev, entry,
|
|
"device driver failed to check map error"
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped as %s]",
|
|
ref->dev_addr, ref->size,
|
|
type2name[entry->type]);
|
|
}
|
|
|
|
hash_bucket_del(entry);
|
|
put_hash_bucket(bucket, flags);
|
|
|
|
/*
|
|
* Free the entry outside of bucket_lock to avoid ABBA deadlocks
|
|
* between that and radix_lock.
|
|
*/
|
|
dma_entry_free(entry);
|
|
}
|
|
|
|
static void check_for_stack(struct device *dev,
|
|
struct page *page, size_t offset)
|
|
{
|
|
void *addr;
|
|
struct vm_struct *stack_vm_area = task_stack_vm_area(current);
|
|
|
|
if (!stack_vm_area) {
|
|
/* Stack is direct-mapped. */
|
|
if (PageHighMem(page))
|
|
return;
|
|
addr = page_address(page) + offset;
|
|
if (object_is_on_stack(addr))
|
|
err_printk(dev, NULL, "device driver maps memory from stack [addr=%p]\n", addr);
|
|
} else {
|
|
/* Stack is vmalloced. */
|
|
int i;
|
|
|
|
for (i = 0; i < stack_vm_area->nr_pages; i++) {
|
|
if (page != stack_vm_area->pages[i])
|
|
continue;
|
|
|
|
addr = (u8 *)current->stack + i * PAGE_SIZE + offset;
|
|
err_printk(dev, NULL, "device driver maps memory from stack [probable addr=%p]\n", addr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline bool overlap(void *addr, unsigned long len, void *start, void *end)
|
|
{
|
|
unsigned long a1 = (unsigned long)addr;
|
|
unsigned long b1 = a1 + len;
|
|
unsigned long a2 = (unsigned long)start;
|
|
unsigned long b2 = (unsigned long)end;
|
|
|
|
return !(b1 <= a2 || a1 >= b2);
|
|
}
|
|
|
|
static void check_for_illegal_area(struct device *dev, void *addr, unsigned long len)
|
|
{
|
|
if (overlap(addr, len, _stext, _etext) ||
|
|
overlap(addr, len, __start_rodata, __end_rodata))
|
|
err_printk(dev, NULL, "device driver maps memory from kernel text or rodata [addr=%p] [len=%lu]\n", addr, len);
|
|
}
|
|
|
|
static void check_sync(struct device *dev,
|
|
struct dma_debug_entry *ref,
|
|
bool to_cpu)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
struct hash_bucket *bucket;
|
|
unsigned long flags;
|
|
|
|
bucket = get_hash_bucket(ref, &flags);
|
|
|
|
entry = bucket_find_contain(&bucket, ref, &flags);
|
|
|
|
if (!entry) {
|
|
err_printk(dev, NULL, "device driver tries "
|
|
"to sync DMA memory it has not allocated "
|
|
"[device address=0x%016llx] [size=%llu bytes]\n",
|
|
(unsigned long long)ref->dev_addr, ref->size);
|
|
goto out;
|
|
}
|
|
|
|
if (ref->size > entry->size) {
|
|
err_printk(dev, entry, "device driver syncs"
|
|
" DMA memory outside allocated range "
|
|
"[device address=0x%016llx] "
|
|
"[allocation size=%llu bytes] "
|
|
"[sync offset+size=%llu]\n",
|
|
entry->dev_addr, entry->size,
|
|
ref->size);
|
|
}
|
|
|
|
if (entry->direction == DMA_BIDIRECTIONAL)
|
|
goto out;
|
|
|
|
if (ref->direction != entry->direction) {
|
|
err_printk(dev, entry, "device driver syncs "
|
|
"DMA memory with different direction "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped with %s] [synced with %s]\n",
|
|
(unsigned long long)ref->dev_addr, entry->size,
|
|
dir2name[entry->direction],
|
|
dir2name[ref->direction]);
|
|
}
|
|
|
|
if (to_cpu && !(entry->direction == DMA_FROM_DEVICE) &&
|
|
!(ref->direction == DMA_TO_DEVICE))
|
|
err_printk(dev, entry, "device driver syncs "
|
|
"device read-only DMA memory for cpu "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped with %s] [synced with %s]\n",
|
|
(unsigned long long)ref->dev_addr, entry->size,
|
|
dir2name[entry->direction],
|
|
dir2name[ref->direction]);
|
|
|
|
if (!to_cpu && !(entry->direction == DMA_TO_DEVICE) &&
|
|
!(ref->direction == DMA_FROM_DEVICE))
|
|
err_printk(dev, entry, "device driver syncs "
|
|
"device write-only DMA memory to device "
|
|
"[device address=0x%016llx] [size=%llu bytes] "
|
|
"[mapped with %s] [synced with %s]\n",
|
|
(unsigned long long)ref->dev_addr, entry->size,
|
|
dir2name[entry->direction],
|
|
dir2name[ref->direction]);
|
|
|
|
/* sg list count can be less than map count when partial cache sync */
|
|
if (ref->sg_call_ents && ref->type == dma_debug_sg &&
|
|
ref->sg_call_ents > entry->sg_call_ents) {
|
|
err_printk(ref->dev, entry, "device driver syncs "
|
|
"DMA sg list count larger than map count "
|
|
"[map count=%d] [sync count=%d]\n",
|
|
entry->sg_call_ents, ref->sg_call_ents);
|
|
}
|
|
|
|
out:
|
|
put_hash_bucket(bucket, flags);
|
|
}
|
|
|
|
static void check_sg_segment(struct device *dev, struct scatterlist *sg)
|
|
{
|
|
#ifdef CONFIG_DMA_API_DEBUG_SG
|
|
unsigned int max_seg = dma_get_max_seg_size(dev);
|
|
u64 start, end, boundary = dma_get_seg_boundary(dev);
|
|
|
|
/*
|
|
* Either the driver forgot to set dma_parms appropriately, or
|
|
* whoever generated the list forgot to check them.
|
|
*/
|
|
if (sg->length > max_seg)
|
|
err_printk(dev, NULL, "mapping sg segment longer than device claims to support [len=%u] [max=%u]\n",
|
|
sg->length, max_seg);
|
|
/*
|
|
* In some cases this could potentially be the DMA API
|
|
* implementation's fault, but it would usually imply that
|
|
* the scatterlist was built inappropriately to begin with.
|
|
*/
|
|
start = sg_dma_address(sg);
|
|
end = start + sg_dma_len(sg) - 1;
|
|
if ((start ^ end) & ~boundary)
|
|
err_printk(dev, NULL, "mapping sg segment across boundary [start=0x%016llx] [end=0x%016llx] [boundary=0x%016llx]\n",
|
|
start, end, boundary);
|
|
#endif
|
|
}
|
|
|
|
void debug_dma_map_single(struct device *dev, const void *addr,
|
|
unsigned long len)
|
|
{
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
if (!virt_addr_valid(addr))
|
|
err_printk(dev, NULL, "device driver maps memory from invalid area [addr=%p] [len=%lu]\n",
|
|
addr, len);
|
|
|
|
if (is_vmalloc_addr(addr))
|
|
err_printk(dev, NULL, "device driver maps memory from vmalloc area [addr=%p] [len=%lu]\n",
|
|
addr, len);
|
|
}
|
|
EXPORT_SYMBOL(debug_dma_map_single);
|
|
|
|
void debug_dma_map_page(struct device *dev, struct page *page, size_t offset,
|
|
size_t size, int direction, dma_addr_t dma_addr)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
if (dma_mapping_error(dev, dma_addr))
|
|
return;
|
|
|
|
entry = dma_entry_alloc();
|
|
if (!entry)
|
|
return;
|
|
|
|
entry->dev = dev;
|
|
entry->type = dma_debug_single;
|
|
entry->pfn = page_to_pfn(page);
|
|
entry->offset = offset;
|
|
entry->dev_addr = dma_addr;
|
|
entry->size = size;
|
|
entry->direction = direction;
|
|
entry->map_err_type = MAP_ERR_NOT_CHECKED;
|
|
|
|
check_for_stack(dev, page, offset);
|
|
|
|
if (!PageHighMem(page)) {
|
|
void *addr = page_address(page) + offset;
|
|
|
|
check_for_illegal_area(dev, addr, size);
|
|
}
|
|
|
|
add_dma_entry(entry);
|
|
}
|
|
|
|
void debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
|
|
{
|
|
struct dma_debug_entry ref;
|
|
struct dma_debug_entry *entry;
|
|
struct hash_bucket *bucket;
|
|
unsigned long flags;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
ref.dev = dev;
|
|
ref.dev_addr = dma_addr;
|
|
bucket = get_hash_bucket(&ref, &flags);
|
|
|
|
list_for_each_entry(entry, &bucket->list, list) {
|
|
if (!exact_match(&ref, entry))
|
|
continue;
|
|
|
|
/*
|
|
* The same physical address can be mapped multiple
|
|
* times. Without a hardware IOMMU this results in the
|
|
* same device addresses being put into the dma-debug
|
|
* hash multiple times too. This can result in false
|
|
* positives being reported. Therefore we implement a
|
|
* best-fit algorithm here which updates the first entry
|
|
* from the hash which fits the reference value and is
|
|
* not currently listed as being checked.
|
|
*/
|
|
if (entry->map_err_type == MAP_ERR_NOT_CHECKED) {
|
|
entry->map_err_type = MAP_ERR_CHECKED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
put_hash_bucket(bucket, flags);
|
|
}
|
|
EXPORT_SYMBOL(debug_dma_mapping_error);
|
|
|
|
void debug_dma_unmap_page(struct device *dev, dma_addr_t addr,
|
|
size_t size, int direction)
|
|
{
|
|
struct dma_debug_entry ref = {
|
|
.type = dma_debug_single,
|
|
.dev = dev,
|
|
.dev_addr = addr,
|
|
.size = size,
|
|
.direction = direction,
|
|
};
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
check_unmap(&ref);
|
|
}
|
|
|
|
void debug_dma_map_sg(struct device *dev, struct scatterlist *sg,
|
|
int nents, int mapped_ents, int direction)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
struct scatterlist *s;
|
|
int i;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
for_each_sg(sg, s, nents, i) {
|
|
check_for_stack(dev, sg_page(s), s->offset);
|
|
if (!PageHighMem(sg_page(s)))
|
|
check_for_illegal_area(dev, sg_virt(s), s->length);
|
|
}
|
|
|
|
for_each_sg(sg, s, mapped_ents, i) {
|
|
entry = dma_entry_alloc();
|
|
if (!entry)
|
|
return;
|
|
|
|
entry->type = dma_debug_sg;
|
|
entry->dev = dev;
|
|
entry->pfn = page_to_pfn(sg_page(s));
|
|
entry->offset = s->offset;
|
|
entry->size = sg_dma_len(s);
|
|
entry->dev_addr = sg_dma_address(s);
|
|
entry->direction = direction;
|
|
entry->sg_call_ents = nents;
|
|
entry->sg_mapped_ents = mapped_ents;
|
|
|
|
check_sg_segment(dev, s);
|
|
|
|
add_dma_entry(entry);
|
|
}
|
|
}
|
|
|
|
static int get_nr_mapped_entries(struct device *dev,
|
|
struct dma_debug_entry *ref)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
struct hash_bucket *bucket;
|
|
unsigned long flags;
|
|
int mapped_ents;
|
|
|
|
bucket = get_hash_bucket(ref, &flags);
|
|
entry = bucket_find_exact(bucket, ref);
|
|
mapped_ents = 0;
|
|
|
|
if (entry)
|
|
mapped_ents = entry->sg_mapped_ents;
|
|
put_hash_bucket(bucket, flags);
|
|
|
|
return mapped_ents;
|
|
}
|
|
|
|
void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist,
|
|
int nelems, int dir)
|
|
{
|
|
struct scatterlist *s;
|
|
int mapped_ents = 0, i;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
for_each_sg(sglist, s, nelems, i) {
|
|
|
|
struct dma_debug_entry ref = {
|
|
.type = dma_debug_sg,
|
|
.dev = dev,
|
|
.pfn = page_to_pfn(sg_page(s)),
|
|
.offset = s->offset,
|
|
.dev_addr = sg_dma_address(s),
|
|
.size = sg_dma_len(s),
|
|
.direction = dir,
|
|
.sg_call_ents = nelems,
|
|
};
|
|
|
|
if (mapped_ents && i >= mapped_ents)
|
|
break;
|
|
|
|
if (!i)
|
|
mapped_ents = get_nr_mapped_entries(dev, &ref);
|
|
|
|
check_unmap(&ref);
|
|
}
|
|
}
|
|
|
|
void debug_dma_alloc_coherent(struct device *dev, size_t size,
|
|
dma_addr_t dma_addr, void *virt)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
if (unlikely(virt == NULL))
|
|
return;
|
|
|
|
/* handle vmalloc and linear addresses */
|
|
if (!is_vmalloc_addr(virt) && !virt_addr_valid(virt))
|
|
return;
|
|
|
|
entry = dma_entry_alloc();
|
|
if (!entry)
|
|
return;
|
|
|
|
entry->type = dma_debug_coherent;
|
|
entry->dev = dev;
|
|
entry->offset = offset_in_page(virt);
|
|
entry->size = size;
|
|
entry->dev_addr = dma_addr;
|
|
entry->direction = DMA_BIDIRECTIONAL;
|
|
|
|
if (is_vmalloc_addr(virt))
|
|
entry->pfn = vmalloc_to_pfn(virt);
|
|
else
|
|
entry->pfn = page_to_pfn(virt_to_page(virt));
|
|
|
|
add_dma_entry(entry);
|
|
}
|
|
|
|
void debug_dma_free_coherent(struct device *dev, size_t size,
|
|
void *virt, dma_addr_t addr)
|
|
{
|
|
struct dma_debug_entry ref = {
|
|
.type = dma_debug_coherent,
|
|
.dev = dev,
|
|
.offset = offset_in_page(virt),
|
|
.dev_addr = addr,
|
|
.size = size,
|
|
.direction = DMA_BIDIRECTIONAL,
|
|
};
|
|
|
|
/* handle vmalloc and linear addresses */
|
|
if (!is_vmalloc_addr(virt) && !virt_addr_valid(virt))
|
|
return;
|
|
|
|
if (is_vmalloc_addr(virt))
|
|
ref.pfn = vmalloc_to_pfn(virt);
|
|
else
|
|
ref.pfn = page_to_pfn(virt_to_page(virt));
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
check_unmap(&ref);
|
|
}
|
|
|
|
void debug_dma_map_resource(struct device *dev, phys_addr_t addr, size_t size,
|
|
int direction, dma_addr_t dma_addr)
|
|
{
|
|
struct dma_debug_entry *entry;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
entry = dma_entry_alloc();
|
|
if (!entry)
|
|
return;
|
|
|
|
entry->type = dma_debug_resource;
|
|
entry->dev = dev;
|
|
entry->pfn = PHYS_PFN(addr);
|
|
entry->offset = offset_in_page(addr);
|
|
entry->size = size;
|
|
entry->dev_addr = dma_addr;
|
|
entry->direction = direction;
|
|
entry->map_err_type = MAP_ERR_NOT_CHECKED;
|
|
|
|
add_dma_entry(entry);
|
|
}
|
|
|
|
void debug_dma_unmap_resource(struct device *dev, dma_addr_t dma_addr,
|
|
size_t size, int direction)
|
|
{
|
|
struct dma_debug_entry ref = {
|
|
.type = dma_debug_resource,
|
|
.dev = dev,
|
|
.dev_addr = dma_addr,
|
|
.size = size,
|
|
.direction = direction,
|
|
};
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
check_unmap(&ref);
|
|
}
|
|
|
|
void debug_dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle,
|
|
size_t size, int direction)
|
|
{
|
|
struct dma_debug_entry ref;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
ref.type = dma_debug_single;
|
|
ref.dev = dev;
|
|
ref.dev_addr = dma_handle;
|
|
ref.size = size;
|
|
ref.direction = direction;
|
|
ref.sg_call_ents = 0;
|
|
|
|
check_sync(dev, &ref, true);
|
|
}
|
|
|
|
void debug_dma_sync_single_for_device(struct device *dev,
|
|
dma_addr_t dma_handle, size_t size,
|
|
int direction)
|
|
{
|
|
struct dma_debug_entry ref;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
ref.type = dma_debug_single;
|
|
ref.dev = dev;
|
|
ref.dev_addr = dma_handle;
|
|
ref.size = size;
|
|
ref.direction = direction;
|
|
ref.sg_call_ents = 0;
|
|
|
|
check_sync(dev, &ref, false);
|
|
}
|
|
|
|
void debug_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
|
|
int nelems, int direction)
|
|
{
|
|
struct scatterlist *s;
|
|
int mapped_ents = 0, i;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
for_each_sg(sg, s, nelems, i) {
|
|
|
|
struct dma_debug_entry ref = {
|
|
.type = dma_debug_sg,
|
|
.dev = dev,
|
|
.pfn = page_to_pfn(sg_page(s)),
|
|
.offset = s->offset,
|
|
.dev_addr = sg_dma_address(s),
|
|
.size = sg_dma_len(s),
|
|
.direction = direction,
|
|
.sg_call_ents = nelems,
|
|
};
|
|
|
|
if (!i)
|
|
mapped_ents = get_nr_mapped_entries(dev, &ref);
|
|
|
|
if (i >= mapped_ents)
|
|
break;
|
|
|
|
check_sync(dev, &ref, true);
|
|
}
|
|
}
|
|
|
|
void debug_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
|
|
int nelems, int direction)
|
|
{
|
|
struct scatterlist *s;
|
|
int mapped_ents = 0, i;
|
|
|
|
if (unlikely(dma_debug_disabled()))
|
|
return;
|
|
|
|
for_each_sg(sg, s, nelems, i) {
|
|
|
|
struct dma_debug_entry ref = {
|
|
.type = dma_debug_sg,
|
|
.dev = dev,
|
|
.pfn = page_to_pfn(sg_page(s)),
|
|
.offset = s->offset,
|
|
.dev_addr = sg_dma_address(s),
|
|
.size = sg_dma_len(s),
|
|
.direction = direction,
|
|
.sg_call_ents = nelems,
|
|
};
|
|
if (!i)
|
|
mapped_ents = get_nr_mapped_entries(dev, &ref);
|
|
|
|
if (i >= mapped_ents)
|
|
break;
|
|
|
|
check_sync(dev, &ref, false);
|
|
}
|
|
}
|
|
|
|
static int __init dma_debug_driver_setup(char *str)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NAME_MAX_LEN - 1; ++i, ++str) {
|
|
current_driver_name[i] = *str;
|
|
if (*str == 0)
|
|
break;
|
|
}
|
|
|
|
if (current_driver_name[0])
|
|
pr_info("enable driver filter for driver [%s]\n",
|
|
current_driver_name);
|
|
|
|
|
|
return 1;
|
|
}
|
|
__setup("dma_debug_driver=", dma_debug_driver_setup);
|