https://source.android.com/docs/security/bulletin/2023-12-01 * tag 'ASB-2023-12-05_4.19-stable' of https://android.googlesource.com/kernel/common: Revert "macsec: use DEV_STATS_INC()" Revert "net: add DEV_STATS_READ() helper" Linux 4.19.300 net: sched: fix race condition in qdisc_graft() iomap: Set all uptodate bits for an Uptodate page scsi: virtio_scsi: limit number of hw queues by nr_cpu_ids drm/amdgpu: fix error handling in amdgpu_bo_list_get() ext4: remove gdb backup copy for meta bg in setup_new_flex_group_blocks ext4: correct return value of ext4_convert_meta_bg ext4: correct offset of gdb backup in non meta_bg group to update_backups ext4: apply umask if ACL support is disabled Revert "net: r8169: Disable multicast filter for RTL8168H and RTL8107E" media: venus: hfi: add checks to handle capabilities from firmware media: venus: hfi: fix the check to handle session buffer requirement media: venus: hfi_parser: Add check to keep the number of codecs within range media: sharp: fix sharp encoding media: lirc: drop trailing space from scancode transmit i2c: i801: fix potential race in i801_block_transaction_byte_by_byte net: dsa: lan9303: consequently nested-lock physical MDIO tty: serial: meson: fix hard LOCKUP on crtscts mode serial: meson: Use platform_get_irq() to get the interrupt tty: serial: meson: retrieve port FIFO size from DT serial: meson: remove redundant initialization of variable id tty: serial: meson: if no alias specified use an available id ALSA: hda/realtek - Enable internal speaker of ASUS K6500ZC ALSA: info: Fix potential deadlock at disconnection parisc/pgtable: Do not drop upper 5 address bits of physical address parisc: Prevent booting 64-bit kernels on PA1.x machines dmaengine: stm32-mdma: correct desc prep when channel running mcb: fix error handling for different scenarios when parsing quota: explicitly forbid quota files from being encrypted jbd2: fix potential data lost in recovering journal raced with synchronizing fs bdev PM: hibernate: Clean up sync_read handling in snapshot_write_next() PM: hibernate: Use __get_safe_page() rather than touching the list mmc: vub300: fix an error code clk: qcom: ipq8074: drop the CLK_SET_RATE_PARENT flag from PLL clocks parisc/pdc: Add width field to struct pdc_model PCI: keystone: Don't discard .probe() callback PCI: keystone: Don't discard .remove() callback genirq/generic_chip: Make irq_remove_generic_chip() irqdomain aware mmc: meson-gx: Remove setting of CMD_CFG_ERROR PCI/sysfs: Protect driver's D3cold preference from user space hvc/xen: fix error path in xen_hvc_init() to always register frontend driver audit: don't WARN_ON_ONCE(!current->mm) in audit_exe_compare() audit: don't take task_lock() in audit_exe_compare() code path KVM: x86: Ignore MSR_AMD64_TW_CFG access randstruct: Fix gcc-plugin performance mode to stay in group media: venus: hfi: add checks to perform sanity on queue pointers cifs: spnego: add ';' in HOST_KEY_LEN macvlan: Don't propagate promisc change to lower dev in passthru net: ethernet: cortina: Fix MTU max setting net: ethernet: cortina: Handle large frames net: ethernet: cortina: Fix max RX frame define ptp: annotate data-race around q->head and q->tail xen/events: fix delayed eoi list handling ppp: limit MRU to 64K tipc: Fix kernel-infoleak due to uninitialized TLV value tty: Fix uninit-value access in ppp_sync_receive() ipvlan: add ipvlan_route_v6_outbound() helper NFSv4.1: fix SP4_MACH_CRED protection for pnfs IO pwm: Fix double shift bug drm/amd/display: Avoid NULL dereference of timing generator gfs2: ignore negated quota changes media: vivid: avoid integer overflow media: gspca: cpia1: shift-out-of-bounds in set_flicker i2c: sun6i-p2wi: Prevent potential division by zero usb: gadget: f_ncm: Always set current gadget in ncm_bind() tty: vcc: Add check for kstrdup() in vcc_probe() HID: Add quirk for Dell Pro Wireless Keyboard and Mouse KM5221W scsi: libfc: Fix potential NULL pointer dereference in fc_lport_ptp_setup() atm: iphase: Do PCI error checks on own line ALSA: hda: Fix possible null-ptr-deref when assigning a stream ARM: 9320/1: fix stack depot IRQ stack filter jfs: fix array-index-out-of-bounds in diAlloc jfs: fix array-index-out-of-bounds in dbFindLeaf fs/jfs: Add validity check for db_maxag and db_agpref fs/jfs: Add check for negative db_l2nbperpage RDMA/hfi1: Use FIELD_GET() to extract Link Width crypto: pcrypt - Fix hungtask for PADATA_RESET selftests/efivarfs: create-read: fix a resource leak drm/amdgpu: Fix a null pointer access when the smc_rreg pointer is NULL drm/amd: Fix UBSAN array-index-out-of-bounds for Polaris and Tonga drm/amd: Fix UBSAN array-index-out-of-bounds for SMU7 platform/x86: thinkpad_acpi: Add battery quirk for Thinkpad X120e Bluetooth: Fix double free in hci_conn_cleanup net: annotate data-races around sk->sk_dst_pending_confirm net: annotate data-races around sk->sk_tx_queue_mapping wifi: ath10k: fix clang-specific fortify warning wifi: ath9k: fix clang-specific fortify warnings wifi: mac80211: don't return unset power in ieee80211_get_tx_power() x86/mm: Drop the 4 MB restriction on minimal NUMA node memory size clocksource/drivers/timer-atmel-tcb: Fix initialization on SAM9 hardware clocksource/drivers/timer-imx-gpt: Fix potential memory leak perf/core: Bail out early if the request AUX area is out of bound locking/ww_mutex/test: Fix potential workqueue corruption Revert "ipvlan: properly track tx_errors" ANDROID: fix up platform_device ABI break Linux 4.19.299 btrfs: use u64 for buffer sizes in the tree search ioctls Revert "mmc: core: Capture correct oemid-bits for eMMC cards" fbdev: fsl-diu-fb: mark wr_reg_wa() static fbdev: imsttfb: fix a resource leak in probe fbdev: imsttfb: Fix error path of imsttfb_probe() netfilter: xt_recent: fix (increase) ipv6 literal buffer length r8169: respect userspace disabling IFF_MULTICAST tg3: power down device only on SYSTEM_POWER_OFF net/smc: fix dangling sock under state SMC_APPFINCLOSEWAIT net/smc: wait for pending work before clcsock release_sock net/smc: postpone release of clcsock net: r8169: Disable multicast filter for RTL8168H and RTL8107E r8169: improve rtl_set_rx_mode dccp/tcp: Call security_inet_conn_request() after setting IPv6 addresses. dccp: Call security_inet_conn_request() after setting IPv4 addresses. tipc: Change nla_policy for bearer-related names to NLA_NUL_STRING llc: verify mac len before reading mac header Input: synaptics-rmi4 - fix use after free in rmi_unregister_function() pwm: brcmstb: Utilize appropriate clock APIs in suspend/resume pwm: sti: Reduce number of allocations and drop usage of chip_data pwm: sti: Avoid conditional gotos media: dvb-usb-v2: af9035: fix missing unlock media: s3c-camif: Avoid inappropriate kfree() media: bttv: fix use after free error due to btv->timeout timer pcmcia: ds: fix possible name leak in error path in pcmcia_device_add() pcmcia: ds: fix refcount leak in pcmcia_device_add() pcmcia: cs: fix possible hung task and memory leak pccardd() f2fs: fix to initialize map.m_pblk in f2fs_precache_extents() dmaengine: pxa_dma: Remove an erroneous BUG_ON() in pxad_free_desc() USB: usbip: fix stub_dev hub disconnect tools: iio: iio_generic_buffer ensure alignment tools: iio: iio_generic_buffer: Fix some integer type and calculation tools: iio: privatize globals and functions in iio_generic_buffer.c file misc: st_core: Do not call kfree_skb() under spin_lock_irqsave() dmaengine: ti: edma: handle irq_of_parse_and_map() errors usb: dwc2: fix possible NULL pointer dereference caused by driver concurrency tty: tty_jobctrl: fix pid memleak in disassociate_ctty() leds: trigger: ledtrig-cpu:: Fix 'output may be truncated' issue for 'cpu' ledtrig-cpu: Limit to 8 CPUs leds: pwm: Don't disable the PWM when the LED should be off leds: pwm: convert to atomic PWM API leds: pwm: simplify if condition mfd: dln2: Fix double put in dln2_probe ASoC: ams-delta.c: use component after check ASoC: Intel: Skylake: Fix mem leak when parsing UUIDs fails sh: bios: Revive earlyprintk support RDMA/hfi1: Workaround truncation compilation error ext4: move 'ix' sanity check to corrent position ARM: 9321/1: memset: cast the constant byte to unsigned char hid: cp2112: Fix duplicate workqueue initialization HID: cp2112: Use irqchip template nd_btt: Make BTT lanes preemptible sched/rt: Provide migrate_disable/enable() inlines hwrng: geode - fix accessing registers clk: scmi: Free scmi_clk allocated when the clocks with invalid info are skipped firmware: ti_sci: Mark driver as non removable ARM: dts: qcom: mdm9615: populate vsdcc fixed regulator drm/rockchip: cdn-dp: Fix some error handling paths in cdn_dp_probe() drm/radeon: possible buffer overflow drm/rockchip: vop: Fix reset of state in duplicate state crtc funcs hwmon: (coretemp) Fix potentially truncated sysfs attribute name platform/x86: wmi: Fix opening of char device platform/x86: wmi: remove unnecessary initializations platform/x86: wmi: Fix probe failure when failing to register WMI devices clk: mediatek: clk-mt2701: Add check for mtk_alloc_clk_data clk: mediatek: clk-mt6797: Add check for mtk_alloc_clk_data clk: npcm7xx: Fix incorrect kfree clk: keystone: pll: fix a couple NULL vs IS_ERR() checks clk: qcom: clk-rcg2: Fix clock rate overflow for high parent frequencies regmap: debugfs: Fix a erroneous check after snprintf() ipvlan: properly track tx_errors net: add DEV_STATS_READ() helper macsec: use DEV_STATS_INC() macsec: Fix traffic counters/statistics ipv6: avoid atomic fragment on GSO packets ACPI: sysfs: Fix create_pnp_modalias() and create_of_modalias() chtls: fix tp->rcv_tstamp initialization thermal: core: prevent potential string overflow can: dev: can_restart(): fix race condition between controller restart and netif_carrier_on() can: dev: can_restart(): don't crash kernel if carrier is OK can: dev: move driver related infrastructure into separate subdir wifi: rtlwifi: fix EDCA limit set by BT coexistence tcp_metrics: do not create an entry from tcp_init_metrics() tcp_metrics: properly set tp->snd_ssthresh in tcp_init_metrics() tcp_metrics: add missing barriers on delete i40e: fix potential memory leaks in i40e_remove() genirq/matrix: Exclude managed interrupts in irq_matrix_allocated() vfs: fix readahead(2) on block devices Linux 4.19.298 tty: 8250: Add support for Intashield IS-100 tty: 8250: Add support for Brainboxes UP cards tty: 8250: Add support for additional Brainboxes UC cards tty: 8250: Remove UC-257 and UC-431 usb: storage: set 1.50 as the lower bcdDevice for older "Super Top" compatibility PCI: Prevent xHCI driver from claiming AMD VanGogh USB3 DRD device remove the sx8 block driver ata: ahci: fix enum constants for gcc-13 net: chelsio: cxgb4: add an error code check in t4_load_phy_fw platform/x86: asus-wmi: Change ASUS_WMI_BRN_DOWN code from 0x20 to 0x2e scsi: mpt3sas: Fix in error path fbdev: uvesafb: Call cn_del_callback() at the end of uvesafb_exit() ASoC: rt5650: fix the wrong result of key button netfilter: nfnetlink_log: silence bogus compiler warning fbdev: atyfb: only use ioremap_uc() on i386 and ia64 Input: synaptics-rmi4 - handle reset delay when using SMBus trsnsport dmaengine: ste_dma40: Fix PM disable depth imbalance in d40_probe irqchip/stm32-exti: add missing DT IRQ flag translation Input: i8042 - add Fujitsu Lifebook E5411 to i8042 quirk table ASoC: simple-card: fixup asoc_simple_probe() error handling MAINTAINERS: r8169: Update path to the driver x86: Fix .brk attribute in linker script rpmsg: Fix possible refcount leak in rpmsg_register_device_override() rpmsg: glink: Release driver_override rpmsg: Fix calling device_lock() on non-initialized device rpmsg: Fix kfree() of static memory on setting driver_override rpmsg: Constify local variable in field store macro driver: platform: Add helper for safer setting of driver_override x86/mm: Fix RESERVE_BRK() for older binutils x86/mm: Simplify RESERVE_BRK() nfsd: lock_rename() needs both directories to live on the same fs f2fs: fix to do sanity check on inode type during garbage collection smbdirect: missing rc checks while waiting for rdma events kobject: Fix slab-out-of-bounds in fill_kobj_path() arm64: fix a concurrency issue in emulation_proc_handler() drm/dp_mst: Fix NULL deref in get_mst_branch_device_by_guid_helper() ARM: 8933/1: replace Sun/Solaris style flag on section directive NFS: Don't call generic_error_remove_page() while holding locks x86/i8259: Skip probing when ACPI/MADT advertises PCAT compatibility iio: exynos-adc: request second interupt only when touchscreen mode is used perf/core: Fix potential NULL deref nvmem: imx: correct nregs for i.MX6UL nvmem: imx: correct nregs for i.MX6SLL i2c: stm32f7: Fix PEC handling in case of SMBUS transfers i2c: muxes: i2c-demux-pinctrl: Use of_get_i2c_adapter_by_node() i2c: muxes: i2c-mux-gpmux: Use of_get_i2c_adapter_by_node() i2c: muxes: i2c-mux-pinctrl: Use of_get_i2c_adapter_by_node() i40e: Fix wrong check for I40E_TXR_FLAGS_WB_ON_ITR gtp: fix fragmentation needed check with gso igb: Fix potential memory leak in igb_add_ethtool_nfc_entry treewide: Spelling fix in comment r8169: fix the KCSAN reported data race in rtl_rx while reading desc->opts1 r8169: fix the KCSAN reported data-race in rtl_tx while reading TxDescArray[entry].opts1 r8169: rename r8169.c to r8169_main.c virtio-mmio: fix memory leak of vm_dev virtio_balloon: Fix endless deflation and inflation on arm64 mcb-lpc: Reallocate memory region to avoid memory overlapping mcb: Return actual parsed size when reading chameleon table selftests/ftrace: Add new test case which checks non unique symbol mmc: core: sdio: hold retuning if sdio in 1-bit mode mmc: sdio: Don't re-initialize powered-on removable SDIO cards at resume Conflicts: drivers/clk/qcom/clk-rcg2.c drivers/leds/leds-pwm.c drivers/mmc/core/sdio.c drivers/rpmsg/qcom_glink_native.c drivers/thermal/thermal_core.c drivers/usb/gadget/function/f_ncm.c Change-Id: I230a2c820e39dd863a874bfc0c7a411896b0ba9c
774 lines
19 KiB
C
774 lines
19 KiB
C
/*
|
|
* Register map access API - debugfs
|
|
*
|
|
* Copyright 2011 Wolfson Microelectronics plc
|
|
*
|
|
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/device.h>
|
|
#include <linux/list.h>
|
|
|
|
#include "internal.h"
|
|
|
|
struct regmap_debugfs_node {
|
|
struct regmap *map;
|
|
const char *name;
|
|
struct list_head link;
|
|
};
|
|
|
|
static unsigned int dummy_index;
|
|
static struct dentry *regmap_debugfs_root;
|
|
static LIST_HEAD(regmap_debugfs_early_list);
|
|
static DEFINE_MUTEX(regmap_debugfs_early_lock);
|
|
|
|
/* Calculate the length of a fixed format */
|
|
static size_t regmap_calc_reg_len(int max_val)
|
|
{
|
|
return snprintf(NULL, 0, "%x", max_val);
|
|
}
|
|
|
|
static ssize_t regmap_name_read_file(struct file *file,
|
|
char __user *user_buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
struct regmap *map = file->private_data;
|
|
const char *name = "nodev";
|
|
int ret;
|
|
char *buf;
|
|
|
|
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (map->dev && map->dev->driver)
|
|
name = map->dev->driver->name;
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%s\n", name);
|
|
if (ret >= PAGE_SIZE) {
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations regmap_name_fops = {
|
|
.open = simple_open,
|
|
.read = regmap_name_read_file,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static void regmap_debugfs_free_dump_cache(struct regmap *map)
|
|
{
|
|
struct regmap_debugfs_off_cache *c;
|
|
|
|
while (!list_empty(&map->debugfs_off_cache)) {
|
|
c = list_first_entry(&map->debugfs_off_cache,
|
|
struct regmap_debugfs_off_cache,
|
|
list);
|
|
list_del(&c->list);
|
|
kfree(c);
|
|
}
|
|
}
|
|
|
|
static bool regmap_printable(struct regmap *map, unsigned int reg)
|
|
{
|
|
if (regmap_precious(map, reg))
|
|
return false;
|
|
|
|
if (!regmap_readable(map, reg) && !regmap_cached(map, reg))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Work out where the start offset maps into register numbers, bearing
|
|
* in mind that we suppress hidden registers.
|
|
*/
|
|
static unsigned int regmap_debugfs_get_dump_start(struct regmap *map,
|
|
unsigned int base,
|
|
loff_t from,
|
|
loff_t *pos)
|
|
{
|
|
struct regmap_debugfs_off_cache *c = NULL;
|
|
loff_t p = 0;
|
|
unsigned int i, ret;
|
|
unsigned int fpos_offset;
|
|
unsigned int reg_offset;
|
|
|
|
/* Suppress the cache if we're using a subrange */
|
|
if (base)
|
|
return base;
|
|
|
|
/*
|
|
* If we don't have a cache build one so we don't have to do a
|
|
* linear scan each time.
|
|
*/
|
|
mutex_lock(&map->cache_lock);
|
|
i = base;
|
|
if (list_empty(&map->debugfs_off_cache)) {
|
|
for (; i <= map->max_register; i += map->reg_stride) {
|
|
/* Skip unprinted registers, closing off cache entry */
|
|
if (!regmap_printable(map, i)) {
|
|
if (c) {
|
|
c->max = p - 1;
|
|
c->max_reg = i - map->reg_stride;
|
|
list_add_tail(&c->list,
|
|
&map->debugfs_off_cache);
|
|
c = NULL;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* No cache entry? Start a new one */
|
|
if (!c) {
|
|
c = kzalloc(sizeof(*c), GFP_KERNEL);
|
|
if (!c) {
|
|
regmap_debugfs_free_dump_cache(map);
|
|
mutex_unlock(&map->cache_lock);
|
|
return base;
|
|
}
|
|
c->min = p;
|
|
c->base_reg = i;
|
|
}
|
|
|
|
p += map->debugfs_tot_len;
|
|
}
|
|
}
|
|
|
|
/* Close the last entry off if we didn't scan beyond it */
|
|
if (c) {
|
|
c->max = p - 1;
|
|
c->max_reg = i - map->reg_stride;
|
|
list_add_tail(&c->list,
|
|
&map->debugfs_off_cache);
|
|
}
|
|
|
|
/*
|
|
* This should never happen; we return above if we fail to
|
|
* allocate and we should never be in this code if there are
|
|
* no registers at all.
|
|
*/
|
|
WARN_ON(list_empty(&map->debugfs_off_cache));
|
|
ret = base;
|
|
|
|
/* Find the relevant block:offset */
|
|
list_for_each_entry(c, &map->debugfs_off_cache, list) {
|
|
if (from >= c->min && from <= c->max) {
|
|
fpos_offset = from - c->min;
|
|
reg_offset = fpos_offset / map->debugfs_tot_len;
|
|
*pos = c->min + (reg_offset * map->debugfs_tot_len);
|
|
mutex_unlock(&map->cache_lock);
|
|
return c->base_reg + (reg_offset * map->reg_stride);
|
|
}
|
|
|
|
*pos = c->max;
|
|
ret = c->max_reg;
|
|
}
|
|
mutex_unlock(&map->cache_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void regmap_calc_tot_len(struct regmap *map,
|
|
void *buf, size_t count)
|
|
{
|
|
/* Calculate the length of a fixed format */
|
|
if (!map->debugfs_tot_len) {
|
|
map->debugfs_reg_len = regmap_calc_reg_len(map->max_register),
|
|
map->debugfs_val_len = 2 * map->format.val_bytes;
|
|
map->debugfs_tot_len = map->debugfs_reg_len +
|
|
map->debugfs_val_len + 3; /* : \n */
|
|
}
|
|
}
|
|
|
|
static ssize_t regmap_read_debugfs(struct regmap *map, unsigned int from,
|
|
unsigned int to, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
size_t buf_pos = 0;
|
|
loff_t p = *ppos;
|
|
ssize_t ret;
|
|
int i;
|
|
char *buf;
|
|
unsigned int val, start_reg;
|
|
|
|
if (*ppos < 0 || !count)
|
|
return -EINVAL;
|
|
|
|
if (count > (PAGE_SIZE << (MAX_ORDER - 1)))
|
|
count = PAGE_SIZE << (MAX_ORDER - 1);
|
|
|
|
buf = kmalloc(count, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
regmap_calc_tot_len(map, buf, count);
|
|
|
|
/* Work out which register we're starting at */
|
|
start_reg = regmap_debugfs_get_dump_start(map, from, *ppos, &p);
|
|
|
|
for (i = start_reg; i <= to; i += map->reg_stride) {
|
|
if (!regmap_readable(map, i) && !regmap_cached(map, i))
|
|
continue;
|
|
|
|
if (regmap_precious(map, i))
|
|
continue;
|
|
|
|
/* If we're in the region the user is trying to read */
|
|
if (p >= *ppos) {
|
|
/* ...but not beyond it */
|
|
if (buf_pos + map->debugfs_tot_len > count)
|
|
break;
|
|
|
|
/* Format the register */
|
|
snprintf(buf + buf_pos, count - buf_pos, "%.*x: ",
|
|
map->debugfs_reg_len, i - from);
|
|
buf_pos += map->debugfs_reg_len + 2;
|
|
|
|
/* Format the value, write all X if we can't read */
|
|
ret = regmap_read(map, i, &val);
|
|
if (ret == 0)
|
|
snprintf(buf + buf_pos, count - buf_pos,
|
|
"%.*x", map->debugfs_val_len, val);
|
|
else
|
|
memset(buf + buf_pos, 'X',
|
|
map->debugfs_val_len);
|
|
buf_pos += 2 * map->format.val_bytes;
|
|
|
|
buf[buf_pos++] = '\n';
|
|
}
|
|
p += map->debugfs_tot_len;
|
|
}
|
|
|
|
ret = buf_pos;
|
|
|
|
if (copy_to_user(user_buf, buf, buf_pos)) {
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
*ppos += buf_pos;
|
|
|
|
out:
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct regmap *map = file->private_data;
|
|
|
|
return regmap_read_debugfs(map, 0, map->max_register, user_buf,
|
|
count, ppos);
|
|
}
|
|
|
|
#ifdef CONFIG_REGMAP_ALLOW_WRITE_DEBUGFS
|
|
/*
|
|
* This can be dangerous especially when we have clients such as
|
|
* PMICs, therefore don't provide any real compile time configuration option
|
|
* for this feature, people who want to use this will need to modify
|
|
* the source code directly.
|
|
*/
|
|
static ssize_t regmap_map_write_file(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[32];
|
|
size_t buf_size;
|
|
char *start = buf;
|
|
unsigned long reg, value;
|
|
struct regmap *map = file->private_data;
|
|
int ret;
|
|
|
|
buf_size = min(count, (sizeof(buf)-1));
|
|
if (copy_from_user(buf, user_buf, buf_size))
|
|
return -EFAULT;
|
|
buf[buf_size] = 0;
|
|
|
|
while (*start == ' ')
|
|
start++;
|
|
reg = simple_strtoul(start, &start, 16);
|
|
while (*start == ' ')
|
|
start++;
|
|
if (kstrtoul(start, 16, &value))
|
|
return -EINVAL;
|
|
|
|
/* Userspace has been fiddling around behind the kernel's back */
|
|
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
|
|
|
ret = regmap_write(map, reg, value);
|
|
if (ret < 0)
|
|
return ret;
|
|
return buf_size;
|
|
}
|
|
#else
|
|
#define regmap_map_write_file NULL
|
|
#endif
|
|
|
|
static const struct file_operations regmap_map_fops = {
|
|
.open = simple_open,
|
|
.read = regmap_map_read_file,
|
|
.write = regmap_map_write_file,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static ssize_t regmap_data_read_file(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct regmap *map = file->private_data;
|
|
int new_count;
|
|
|
|
regmap_calc_tot_len(map, NULL, 0);
|
|
new_count = map->dump_count * map->debugfs_tot_len;
|
|
if (new_count > count)
|
|
new_count = count;
|
|
|
|
if (*ppos == 0)
|
|
*ppos = map->dump_address * map->debugfs_tot_len;
|
|
else if (*ppos >= map->dump_address * map->debugfs_tot_len
|
|
+ map->dump_count * map->debugfs_tot_len)
|
|
return 0;
|
|
else if (*ppos < map->dump_address * map->debugfs_tot_len)
|
|
return 0;
|
|
|
|
return regmap_read_debugfs(map, 0, map->max_register, user_buf,
|
|
new_count, ppos);
|
|
}
|
|
|
|
#ifdef CONFIG_REGMAP_ALLOW_WRITE_DEBUGFS
|
|
static ssize_t regmap_data_write_file(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[32];
|
|
size_t buf_size;
|
|
char *start = buf;
|
|
unsigned long value;
|
|
struct regmap *map = file->private_data;
|
|
int ret;
|
|
|
|
buf_size = min(count, (sizeof(buf)-1));
|
|
if (copy_from_user(buf, user_buf, buf_size))
|
|
return -EFAULT;
|
|
buf[buf_size] = 0;
|
|
|
|
while (*start == ' ')
|
|
start++;
|
|
if (kstrtoul(start, 16, &value))
|
|
return -EINVAL;
|
|
|
|
/* Userspace has been fiddling around behind the kernel's back */
|
|
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
|
|
|
ret = regmap_write(map, map->dump_address, value);
|
|
if (ret < 0)
|
|
return ret;
|
|
return buf_size;
|
|
}
|
|
#else
|
|
#define regmap_data_write_file NULL
|
|
#endif
|
|
|
|
static const struct file_operations regmap_data_fops = {
|
|
.open = simple_open,
|
|
.read = regmap_data_read_file,
|
|
.write = regmap_data_write_file,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static ssize_t regmap_range_read_file(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct regmap_range_node *range = file->private_data;
|
|
struct regmap *map = range->map;
|
|
|
|
return regmap_read_debugfs(map, range->range_min, range->range_max,
|
|
user_buf, count, ppos);
|
|
}
|
|
|
|
static const struct file_operations regmap_range_fops = {
|
|
.open = simple_open,
|
|
.read = regmap_range_read_file,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static ssize_t regmap_reg_ranges_read_file(struct file *file,
|
|
char __user *user_buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
struct regmap *map = file->private_data;
|
|
struct regmap_debugfs_off_cache *c;
|
|
loff_t p = 0;
|
|
size_t buf_pos = 0;
|
|
char *buf;
|
|
char *entry;
|
|
int ret;
|
|
unsigned entry_len;
|
|
|
|
if (*ppos < 0 || !count)
|
|
return -EINVAL;
|
|
|
|
if (count > (PAGE_SIZE << (MAX_ORDER - 1)))
|
|
count = PAGE_SIZE << (MAX_ORDER - 1);
|
|
|
|
buf = kmalloc(count, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
entry = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!entry) {
|
|
kfree(buf);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* While we are at it, build the register dump cache
|
|
* now so the read() operation on the `registers' file
|
|
* can benefit from using the cache. We do not care
|
|
* about the file position information that is contained
|
|
* in the cache, just about the actual register blocks */
|
|
regmap_calc_tot_len(map, buf, count);
|
|
regmap_debugfs_get_dump_start(map, 0, *ppos, &p);
|
|
|
|
/* Reset file pointer as the fixed-format of the `registers'
|
|
* file is not compatible with the `range' file */
|
|
p = 0;
|
|
mutex_lock(&map->cache_lock);
|
|
list_for_each_entry(c, &map->debugfs_off_cache, list) {
|
|
entry_len = snprintf(entry, PAGE_SIZE, "%x-%x\n",
|
|
c->base_reg, c->max_reg);
|
|
if (p >= *ppos) {
|
|
if (buf_pos + entry_len > count)
|
|
break;
|
|
memcpy(buf + buf_pos, entry, entry_len);
|
|
buf_pos += entry_len;
|
|
}
|
|
p += entry_len;
|
|
}
|
|
mutex_unlock(&map->cache_lock);
|
|
|
|
kfree(entry);
|
|
ret = buf_pos;
|
|
|
|
if (copy_to_user(user_buf, buf, buf_pos)) {
|
|
ret = -EFAULT;
|
|
goto out_buf;
|
|
}
|
|
|
|
*ppos += buf_pos;
|
|
out_buf:
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations regmap_reg_ranges_fops = {
|
|
.open = simple_open,
|
|
.read = regmap_reg_ranges_read_file,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static int regmap_access_show(struct seq_file *s, void *ignored)
|
|
{
|
|
struct regmap *map = s->private;
|
|
int i, reg_len;
|
|
|
|
reg_len = regmap_calc_reg_len(map->max_register);
|
|
|
|
for (i = 0; i <= map->max_register; i += map->reg_stride) {
|
|
/* Ignore registers which are neither readable nor writable */
|
|
if (!regmap_readable(map, i) && !regmap_writeable(map, i))
|
|
continue;
|
|
|
|
/* Format the register */
|
|
seq_printf(s, "%.*x: %c %c %c %c\n", reg_len, i,
|
|
regmap_readable(map, i) ? 'y' : 'n',
|
|
regmap_writeable(map, i) ? 'y' : 'n',
|
|
regmap_volatile(map, i) ? 'y' : 'n',
|
|
regmap_precious(map, i) ? 'y' : 'n');
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int access_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, regmap_access_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations regmap_access_fops = {
|
|
.open = access_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static ssize_t regmap_cache_only_write_file(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct regmap *map = container_of(file->private_data,
|
|
struct regmap, cache_only);
|
|
bool new_val, require_sync = false;
|
|
int err;
|
|
|
|
err = kstrtobool_from_user(user_buf, count, &new_val);
|
|
/* Ignore malforned data like debugfs_write_file_bool() */
|
|
if (err)
|
|
return count;
|
|
|
|
err = debugfs_file_get(file->f_path.dentry);
|
|
if (err)
|
|
return err;
|
|
|
|
map->lock(map->lock_arg);
|
|
|
|
if (new_val && !map->cache_only) {
|
|
dev_warn(map->dev, "debugfs cache_only=Y forced\n");
|
|
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
|
} else if (!new_val && map->cache_only) {
|
|
dev_warn(map->dev, "debugfs cache_only=N forced: syncing cache\n");
|
|
require_sync = true;
|
|
}
|
|
map->cache_only = new_val;
|
|
|
|
map->unlock(map->lock_arg);
|
|
debugfs_file_put(file->f_path.dentry);
|
|
|
|
if (require_sync) {
|
|
err = regcache_sync(map);
|
|
if (err)
|
|
dev_err(map->dev, "Failed to sync cache %d\n", err);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations regmap_cache_only_fops = {
|
|
.open = simple_open,
|
|
.read = debugfs_read_file_bool,
|
|
.write = regmap_cache_only_write_file,
|
|
};
|
|
|
|
static ssize_t regmap_cache_bypass_write_file(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct regmap *map = container_of(file->private_data,
|
|
struct regmap, cache_bypass);
|
|
bool new_val;
|
|
int err;
|
|
|
|
err = kstrtobool_from_user(user_buf, count, &new_val);
|
|
/* Ignore malforned data like debugfs_write_file_bool() */
|
|
if (err)
|
|
return count;
|
|
|
|
err = debugfs_file_get(file->f_path.dentry);
|
|
if (err)
|
|
return err;
|
|
|
|
map->lock(map->lock_arg);
|
|
|
|
if (new_val && !map->cache_bypass) {
|
|
dev_warn(map->dev, "debugfs cache_bypass=Y forced\n");
|
|
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
|
} else if (!new_val && map->cache_bypass) {
|
|
dev_warn(map->dev, "debugfs cache_bypass=N forced\n");
|
|
}
|
|
map->cache_bypass = new_val;
|
|
|
|
map->unlock(map->lock_arg);
|
|
debugfs_file_put(file->f_path.dentry);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations regmap_cache_bypass_fops = {
|
|
.open = simple_open,
|
|
.read = debugfs_read_file_bool,
|
|
.write = regmap_cache_bypass_write_file,
|
|
};
|
|
|
|
void regmap_debugfs_init(struct regmap *map, const char *name)
|
|
{
|
|
struct rb_node *next;
|
|
struct regmap_range_node *range_node;
|
|
const char *devname = "dummy";
|
|
|
|
/*
|
|
* Userspace can initiate reads from the hardware over debugfs.
|
|
* Normally internal regmap structures and buffers are protected with
|
|
* a mutex or a spinlock, but if the regmap owner decided to disable
|
|
* all locking mechanisms, this is no longer the case. For safety:
|
|
* don't create the debugfs entries if locking is disabled.
|
|
*/
|
|
if (map->debugfs_disable) {
|
|
dev_dbg(map->dev, "regmap locking disabled - not creating debugfs entries\n");
|
|
return;
|
|
}
|
|
|
|
/* If we don't have the debugfs root yet, postpone init */
|
|
if (!regmap_debugfs_root) {
|
|
struct regmap_debugfs_node *node;
|
|
node = kzalloc(sizeof(*node), GFP_KERNEL);
|
|
if (!node)
|
|
return;
|
|
node->map = map;
|
|
node->name = name;
|
|
mutex_lock(®map_debugfs_early_lock);
|
|
list_add(&node->link, ®map_debugfs_early_list);
|
|
mutex_unlock(®map_debugfs_early_lock);
|
|
return;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&map->debugfs_off_cache);
|
|
mutex_init(&map->cache_lock);
|
|
|
|
if (map->dev)
|
|
devname = dev_name(map->dev);
|
|
|
|
if (name) {
|
|
if (!map->debugfs_name) {
|
|
map->debugfs_name = kasprintf(GFP_KERNEL, "%s-%s",
|
|
devname, name);
|
|
if (!map->debugfs_name)
|
|
return;
|
|
}
|
|
name = map->debugfs_name;
|
|
} else {
|
|
name = devname;
|
|
}
|
|
|
|
if (!strcmp(name, "dummy")) {
|
|
kfree(map->debugfs_name);
|
|
map->debugfs_name = kasprintf(GFP_KERNEL, "dummy%d",
|
|
dummy_index);
|
|
if (!map->debugfs_name)
|
|
return;
|
|
name = map->debugfs_name;
|
|
dummy_index++;
|
|
}
|
|
|
|
map->debugfs = debugfs_create_dir(name, regmap_debugfs_root);
|
|
if (!map->debugfs) {
|
|
dev_warn(map->dev,
|
|
"Failed to create %s debugfs directory\n", name);
|
|
|
|
kfree(map->debugfs_name);
|
|
map->debugfs_name = NULL;
|
|
return;
|
|
}
|
|
|
|
debugfs_create_file("name", 0400, map->debugfs,
|
|
map, ®map_name_fops);
|
|
|
|
debugfs_create_file("range", 0400, map->debugfs,
|
|
map, ®map_reg_ranges_fops);
|
|
|
|
if (map->max_register || regmap_readable(map, 0)) {
|
|
umode_t registers_mode;
|
|
|
|
#ifdef CONFIG_REGMAP_ALLOW_WRITE_DEBUGFS
|
|
registers_mode = 0600;
|
|
#else
|
|
registers_mode = 0400;
|
|
#endif
|
|
|
|
debugfs_create_file("registers", registers_mode, map->debugfs,
|
|
map, ®map_map_fops);
|
|
|
|
debugfs_create_x32("address", 0600, map->debugfs,
|
|
&map->dump_address);
|
|
map->dump_count = 1;
|
|
debugfs_create_u32("count", 0600, map->debugfs,
|
|
&map->dump_count);
|
|
debugfs_create_file("data", registers_mode, map->debugfs,
|
|
map, ®map_data_fops);
|
|
|
|
debugfs_create_file("access", 0400, map->debugfs,
|
|
map, ®map_access_fops);
|
|
}
|
|
|
|
if (map->cache_type) {
|
|
debugfs_create_file("cache_only", 0600, map->debugfs,
|
|
&map->cache_only, ®map_cache_only_fops);
|
|
debugfs_create_bool("cache_dirty", 0400, map->debugfs,
|
|
&map->cache_dirty);
|
|
debugfs_create_file("cache_bypass", 0600, map->debugfs,
|
|
&map->cache_bypass,
|
|
®map_cache_bypass_fops);
|
|
}
|
|
|
|
next = rb_first(&map->range_tree);
|
|
while (next) {
|
|
range_node = rb_entry(next, struct regmap_range_node, node);
|
|
|
|
if (range_node->name)
|
|
debugfs_create_file(range_node->name, 0400,
|
|
map->debugfs, range_node,
|
|
®map_range_fops);
|
|
|
|
next = rb_next(&range_node->node);
|
|
}
|
|
|
|
if (map->cache_ops && map->cache_ops->debugfs_init)
|
|
map->cache_ops->debugfs_init(map);
|
|
}
|
|
|
|
void regmap_debugfs_exit(struct regmap *map)
|
|
{
|
|
if (map->debugfs) {
|
|
debugfs_remove_recursive(map->debugfs);
|
|
mutex_lock(&map->cache_lock);
|
|
regmap_debugfs_free_dump_cache(map);
|
|
mutex_unlock(&map->cache_lock);
|
|
kfree(map->debugfs_name);
|
|
map->debugfs_name = NULL;
|
|
} else {
|
|
struct regmap_debugfs_node *node, *tmp;
|
|
|
|
mutex_lock(®map_debugfs_early_lock);
|
|
list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list,
|
|
link) {
|
|
if (node->map == map) {
|
|
list_del(&node->link);
|
|
kfree(node);
|
|
}
|
|
}
|
|
mutex_unlock(®map_debugfs_early_lock);
|
|
}
|
|
}
|
|
|
|
void regmap_debugfs_initcall(void)
|
|
{
|
|
struct regmap_debugfs_node *node, *tmp;
|
|
|
|
regmap_debugfs_root = debugfs_create_dir("regmap", NULL);
|
|
if (!regmap_debugfs_root) {
|
|
pr_warn("regmap: Failed to create debugfs root\n");
|
|
return;
|
|
}
|
|
|
|
mutex_lock(®map_debugfs_early_lock);
|
|
list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) {
|
|
regmap_debugfs_init(node->map, node->name);
|
|
list_del(&node->link);
|
|
kfree(node);
|
|
}
|
|
mutex_unlock(®map_debugfs_early_lock);
|
|
}
|