]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
PCI: dwc: Add debugfs based Silicon Debug support for DWC
authorShradha Todi <shradha.t@samsung.com>
Fri, 21 Feb 2025 13:15:46 +0000 (18:45 +0530)
committerKrzysztof Wilczyński <kwilczynski@kernel.org>
Thu, 6 Mar 2025 08:55:47 +0000 (08:55 +0000)
Add support to provide Silicon Debug interface to userspace.

This set of debug registers are part of the RAS DES feature present in
DesignWare PCIe controllers.

Co-developed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Signed-off-by: Shradha Todi <shradha.t@samsung.com>
Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Reviewed-by: Fan Ni <fan.ni@samsung.com>
Tested-by: Hrishikesh Deleep <hrishikesh.d@samsung.com>
Link: https://lore.kernel.org/r/20250221131548.59616-4-shradha.t@samsung.com
[kwilczynski: commit log, tidy up Kconfig and drop "default y", tidy up
code comments, squashed patch that fixes a NULL pointer dereference when
debugfs is already unavailable during clean-up from
https://lore.kernel.org/linux-pci/20250225171239.19574-2-manivannan.sadhasivam@linaro.org,
refactor dwc_pcie_debugfs_init() to not return errors, squashed patch that
changes how lack of the RAS DES capability is handled from
https://lore.kernel.org/linux-pci/20250304151814.6xu7cbpwpqrvcad5@thinkpad]
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
Documentation/ABI/testing/debugfs-dwc-pcie [new file with mode: 0644]
drivers/pci/controller/dwc/Kconfig
drivers/pci/controller/dwc/Makefile
drivers/pci/controller/dwc/pcie-designware-debugfs.c [new file with mode: 0644]
drivers/pci/controller/dwc/pcie-designware-ep.c
drivers/pci/controller/dwc/pcie-designware-host.c
drivers/pci/controller/dwc/pcie-designware.c
drivers/pci/controller/dwc/pcie-designware.h
include/linux/pcie-dwc.h

diff --git a/Documentation/ABI/testing/debugfs-dwc-pcie b/Documentation/ABI/testing/debugfs-dwc-pcie
new file mode 100644 (file)
index 0000000..5b87471
--- /dev/null
@@ -0,0 +1,13 @@
+What:          /sys/kernel/debug/dwc_pcie_<dev>/rasdes_debug/lane_detect
+Date:          February 2025
+Contact:       Shradha Todi <shradha.t@samsung.com>
+Description:   (RW) Write the lane number to be checked for detection. Read
+               will return whether PHY indicates receiver detection on the
+               selected lane. The default selected lane is Lane0.
+
+What:          /sys/kernel/debug/dwc_pcie_<dev>/rasdes_debug/rx_valid
+Date:          February 2025
+Contact:       Shradha Todi <shradha.t@samsung.com>
+Description:   (RW) Write the lane number to be checked as valid or invalid.
+               Read will return the status of PIPE RXVALID signal of the
+               selected lane. The default selected lane is Lane0.
index b6d6778b0698b2ab22ef69f6c8d8cd5619ede41f..3f13669015cc5498be4004355f906c1f10e08329 100644 (file)
@@ -6,6 +6,16 @@ menu "DesignWare-based PCIe controllers"
 config PCIE_DW
        bool
 
+config PCIE_DW_DEBUGFS
+       bool "DesignWare PCIe debugfs entries"
+       depends on DEBUG_FS
+       depends on PCIE_DW_HOST || PCIE_DW_EP
+       help
+         Say Y here to enable debugfs entries for the PCIe controller. These
+         entries provide various debug features related to the controller and
+         expose the RAS DES capabilities such as Silicon Debug, Error Injection
+         and Statistical Counters.
+
 config PCIE_DW_HOST
        bool
        select PCIE_DW
index a8308d9ea9861f633a89407321120f6f6857a428..54565eedc52cc36bc393e257d093c4671aff7b39 100644 (file)
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_PCIE_DW) += pcie-designware.o
+obj-$(CONFIG_PCIE_DW_DEBUGFS) += pcie-designware-debugfs.o
 obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
 obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
 obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c
new file mode 100644 (file)
index 0000000..c86befa
--- /dev/null
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DesignWare PCIe controller debugfs driver
+ *
+ * Copyright (C) 2025 Samsung Electronics Co., Ltd.
+ *              http://www.samsung.com
+ *
+ * Author: Shradha Todi <shradha.t@samsung.com>
+ */
+
+#include <linux/debugfs.h>
+
+#include "pcie-designware.h"
+
+#define SD_STATUS_L1LANE_REG           0xb0
+#define PIPE_RXVALID                   BIT(18)
+#define PIPE_DETECT_LANE               BIT(17)
+#define LANE_SELECT                    GENMASK(3, 0)
+
+#define DWC_DEBUGFS_BUF_MAX            128
+
+/**
+ * struct dwc_pcie_rasdes_info - Stores controller common information
+ * @ras_cap_offset: RAS DES vendor specific extended capability offset
+ * @reg_event_lock: Mutex used for RAS DES shadow event registers
+ *
+ * Any parameter constant to all files of the debugfs hierarchy for a single
+ * controller will be stored in this struct. It is allocated and assigned to
+ * controller specific struct dw_pcie during initialization.
+ */
+struct dwc_pcie_rasdes_info {
+       u32 ras_cap_offset;
+       struct mutex reg_event_lock;
+};
+
+static ssize_t lane_detect_read(struct file *file, char __user *buf,
+                               size_t count, loff_t *ppos)
+{
+       struct dw_pcie *pci = file->private_data;
+       struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
+       char debugfs_buf[DWC_DEBUGFS_BUF_MAX];
+       ssize_t pos;
+       u32 val;
+
+       val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG);
+       val = FIELD_GET(PIPE_DETECT_LANE, val);
+       if (val)
+               pos = scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX, "Lane Detected\n");
+       else
+               pos = scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX, "Lane Undetected\n");
+
+       return simple_read_from_buffer(buf, count, ppos, debugfs_buf, pos);
+}
+
+static ssize_t lane_detect_write(struct file *file, const char __user *buf,
+                                size_t count, loff_t *ppos)
+{
+       struct dw_pcie *pci = file->private_data;
+       struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
+       u32 lane, val;
+
+       val = kstrtou32_from_user(buf, count, 0, &lane);
+       if (val)
+               return val;
+
+       val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG);
+       val &= ~(LANE_SELECT);
+       val |= FIELD_PREP(LANE_SELECT, lane);
+       dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG, val);
+
+       return count;
+}
+
+static ssize_t rx_valid_read(struct file *file, char __user *buf,
+                            size_t count, loff_t *ppos)
+{
+       struct dw_pcie *pci = file->private_data;
+       struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
+       char debugfs_buf[DWC_DEBUGFS_BUF_MAX];
+       ssize_t pos;
+       u32 val;
+
+       val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG);
+       val = FIELD_GET(PIPE_RXVALID, val);
+       if (val)
+               pos = scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX, "RX Valid\n");
+       else
+               pos = scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX, "RX Invalid\n");
+
+       return simple_read_from_buffer(buf, count, ppos, debugfs_buf, pos);
+}
+
+static ssize_t rx_valid_write(struct file *file, const char __user *buf,
+                             size_t count, loff_t *ppos)
+{
+       return lane_detect_write(file, buf, count, ppos);
+}
+
+#define dwc_debugfs_create(name)                       \
+debugfs_create_file(#name, 0644, rasdes_debug, pci,    \
+                       &dbg_ ## name ## _fops)
+
+#define DWC_DEBUGFS_FOPS(name)                                 \
+static const struct file_operations dbg_ ## name ## _fops = {  \
+       .open = simple_open,                            \
+       .read = name ## _read,                          \
+       .write = name ## _write                         \
+}
+
+DWC_DEBUGFS_FOPS(lane_detect);
+DWC_DEBUGFS_FOPS(rx_valid);
+
+static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci)
+{
+       struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
+
+       mutex_destroy(&rinfo->reg_event_lock);
+}
+
+static int dwc_pcie_rasdes_debugfs_init(struct dw_pcie *pci, struct dentry *dir)
+{
+       struct dentry *rasdes_debug;
+       struct dwc_pcie_rasdes_info *rasdes_info;
+       struct device *dev = pci->dev;
+       int ras_cap;
+
+       /*
+        * If a given SoC has no RAS DES capability, the following call is
+        * bound to return an error, breaking some existing platforms. So,
+        * return 0 here, as this is not necessarily an error.
+        */
+       ras_cap = dw_pcie_find_rasdes_capability(pci);
+       if (!ras_cap) {
+               dev_dbg(dev, "no RAS DES capability available\n");
+               return 0;
+       }
+
+       rasdes_info = devm_kzalloc(dev, sizeof(*rasdes_info), GFP_KERNEL);
+       if (!rasdes_info)
+               return -ENOMEM;
+
+       /* Create subdirectories for Debug, Error Injection, Statistics. */
+       rasdes_debug = debugfs_create_dir("rasdes_debug", dir);
+
+       mutex_init(&rasdes_info->reg_event_lock);
+       rasdes_info->ras_cap_offset = ras_cap;
+       pci->debugfs->rasdes_info = rasdes_info;
+
+       /* Create debugfs files for Debug subdirectory. */
+       dwc_debugfs_create(lane_detect);
+       dwc_debugfs_create(rx_valid);
+
+       return 0;
+}
+
+void dwc_pcie_debugfs_deinit(struct dw_pcie *pci)
+{
+       if (!pci->debugfs)
+               return;
+
+       dwc_pcie_rasdes_debugfs_deinit(pci);
+       debugfs_remove_recursive(pci->debugfs->debug_dir);
+}
+
+void dwc_pcie_debugfs_init(struct dw_pcie *pci)
+{
+       char dirname[DWC_DEBUGFS_BUF_MAX];
+       struct device *dev = pci->dev;
+       struct debugfs_info *debugfs;
+       struct dentry *dir;
+       int err;
+
+       /* Create main directory for each platform driver. */
+       snprintf(dirname, DWC_DEBUGFS_BUF_MAX, "dwc_pcie_%s", dev_name(dev));
+       dir = debugfs_create_dir(dirname, NULL);
+       debugfs = devm_kzalloc(dev, sizeof(*debugfs), GFP_KERNEL);
+       if (!debugfs)
+               return;
+
+       debugfs->debug_dir = dir;
+       pci->debugfs = debugfs;
+       err = dwc_pcie_rasdes_debugfs_init(pci, dir);
+       if (err)
+               dev_err(dev, "failed to initialize RAS DES debugfs, err=%d\n",
+                       err);
+}
index 8e07d432e74f206085a75c76d9e95f8a3ae61305..11ff292ca87de4a665942d1b957ce97a6212a7a5 100644 (file)
@@ -666,6 +666,7 @@ void dw_pcie_ep_cleanup(struct dw_pcie_ep *ep)
 {
        struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
 
+       dwc_pcie_debugfs_deinit(pci);
        dw_pcie_edma_remove(pci);
 }
 EXPORT_SYMBOL_GPL(dw_pcie_ep_cleanup);
@@ -837,6 +838,8 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep *ep)
 
        dw_pcie_ep_init_non_sticky_registers(pci);
 
+       dwc_pcie_debugfs_init(pci);
+
        return 0;
 
 err_remove_edma:
index ffaded8f2df7bcddfbc3fcc488019e42fdf829cc..6501fb062c70e56eb301ca71fcd642f7be33a252 100644 (file)
@@ -548,6 +548,8 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
        if (pp->ops->post_init)
                pp->ops->post_init(pp);
 
+       dwc_pcie_debugfs_init(pci);
+
        return 0;
 
 err_stop_link:
@@ -572,6 +574,8 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
 {
        struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
 
+       dwc_pcie_debugfs_deinit(pci);
+
        pci_stop_root_bus(pp->bridge->bus);
        pci_remove_root_bus(pp->bridge->bus);
 
index a7c0671c6715c1b1d420d2a91606943dc2a42ba8..3d1d95d9e38057dd1389e2d57d701ce4f4fa6f7f 100644 (file)
@@ -323,6 +323,12 @@ static u16 dw_pcie_find_vsec_capability(struct dw_pcie *pci,
        return 0;
 }
 
+u16 dw_pcie_find_rasdes_capability(struct dw_pcie *pci)
+{
+       return dw_pcie_find_vsec_capability(pci, dwc_pcie_rasdes_vsec_ids);
+}
+EXPORT_SYMBOL_GPL(dw_pcie_find_rasdes_capability);
+
 int dw_pcie_read(void __iomem *addr, int size, u32 *val)
 {
        if (!IS_ALIGNED((uintptr_t)addr, size)) {
index 501d9ddfea1630be8544d1493bd457f0d179885b..dd129718fb41e0f9404bdc5a1abb86dd2b442c5d 100644 (file)
@@ -437,6 +437,11 @@ struct dw_pcie_ops {
        void    (*stop_link)(struct dw_pcie *pcie);
 };
 
+struct debugfs_info {
+       struct dentry           *debug_dir;
+       void                    *rasdes_info;
+};
+
 struct dw_pcie {
        struct device           *dev;
        void __iomem            *dbi_base;
@@ -465,6 +470,7 @@ struct dw_pcie {
        struct reset_control_bulk_data  core_rsts[DW_PCIE_NUM_CORE_RSTS];
        struct gpio_desc                *pe_rst;
        bool                    suspended;
+       struct debugfs_info     *debugfs;
 };
 
 #define to_dw_pcie_from_pp(port) container_of((port), struct dw_pcie, pp)
@@ -478,6 +484,7 @@ void dw_pcie_version_detect(struct dw_pcie *pci);
 
 u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap);
 u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap);
+u16 dw_pcie_find_rasdes_capability(struct dw_pcie *pci);
 
 int dw_pcie_read(void __iomem *addr, int size, u32 *val);
 int dw_pcie_write(void __iomem *addr, int size, u32 val);
@@ -806,4 +813,17 @@ dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, u8 func_no)
        return NULL;
 }
 #endif
+
+#ifdef CONFIG_PCIE_DW_DEBUGFS
+void dwc_pcie_debugfs_init(struct dw_pcie *pci);
+void dwc_pcie_debugfs_deinit(struct dw_pcie *pci);
+#else
+static inline void dwc_pcie_debugfs_init(struct dw_pcie *pci)
+{
+}
+static inline void dwc_pcie_debugfs_deinit(struct dw_pcie *pci)
+{
+}
+#endif
+
 #endif /* _PCIE_DESIGNWARE_H */
index 6e4e1307ad6e2617e8b0a1d0d66d3262b3df830c..007b3f1b7b17bee742d33a99af2f139da0862012 100644 (file)
@@ -28,6 +28,8 @@ static const struct dwc_pcie_vsec_id dwc_pcie_rasdes_vsec_ids[] = {
          .vsec_id = 0x02, .vsec_rev = 0x4 },
        { .vendor_id = PCI_VENDOR_ID_QCOM,
          .vsec_id = 0x02, .vsec_rev = 0x4 },
+       { .vendor_id = PCI_VENDOR_ID_SAMSUNG,
+         .vsec_id = 0x02, .vsec_rev = 0x4 },
        {}
 };