Message ID | 20250214105007.97582-3-shradha.t@samsung.com (mailing list archive) |
---|---|
State | New |
Delegated to: | Krzysztof Wilczyński |
Headers | show |
Series | Add support for debugfs based RAS DES feature in PCIe DW | expand |
On Fri, Feb 14, 2025 at 04:20:05PM +0530, Shradha Todi wrote: > Add support to provide silicon debug interface to userspace. This set > of debug registers are part of the RASDES feature present in DesignWare > PCIe controllers. > > Signed-off-by: Shradha Todi <shradha.t@samsung.com> > --- > Documentation/ABI/testing/debugfs-dwc-pcie | 13 ++ > drivers/pci/controller/dwc/Kconfig | 10 + > drivers/pci/controller/dwc/Makefile | 1 + > .../controller/dwc/pcie-designware-debugfs.c | 207 ++++++++++++++++++ > .../pci/controller/dwc/pcie-designware-ep.c | 5 + > .../pci/controller/dwc/pcie-designware-host.c | 6 + > drivers/pci/controller/dwc/pcie-designware.h | 20 ++ > 7 files changed, 262 insertions(+) > create mode 100644 Documentation/ABI/testing/debugfs-dwc-pcie > create mode 100644 drivers/pci/controller/dwc/pcie-designware-debugfs.c > > diff --git a/Documentation/ABI/testing/debugfs-dwc-pcie b/Documentation/ABI/testing/debugfs-dwc-pcie > new file mode 100644 > index 000000000000..e8ed34e988ef > --- /dev/null > +++ b/Documentation/ABI/testing/debugfs-dwc-pcie > @@ -0,0 +1,13 @@ > +What: /sys/kernel/debug/dwc_pcie_<dev>/rasdes_debug/lane_detect > +Date: Feburary 2025 Please align these fields > +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: Feburary 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. > diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig > index b6d6778b0698..48a10428a492 100644 > --- a/drivers/pci/controller/dwc/Kconfig > +++ b/drivers/pci/controller/dwc/Kconfig > @@ -6,6 +6,16 @@ menu "DesignWare-based PCIe controllers" > config PCIE_DW > bool > > +config PCIE_DW_DEBUGFS > + default y > + depends on DEBUG_FS > + depends on PCIE_DW_HOST || PCIE_DW_EP > + bool "DWC PCIe debugfs entries" > + help > + Enables debugfs entries for the DW PCIe Controller. These entries > + provide all debug features related to DW controller including the RAS > + DES features to help in debug, error injection and statistical counters. > + > config PCIE_DW_HOST > bool > select PCIE_DW > diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile > index a8308d9ea986..54565eedc52c 100644 > --- a/drivers/pci/controller/dwc/Makefile > +++ b/drivers/pci/controller/dwc/Makefile > @@ -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 > index 000000000000..fe799d36fa7f > --- /dev/null > +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c > @@ -0,0 +1,207 @@ > +// 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_vsec_id { > + u16 vendor_id; > + u16 vsec_id; > + u8 vsec_rev; > +}; > + > +/* > + * VSEC IDs are allocated by the vendor, so a given ID may mean different > + * things to different vendors. See PCIe r6.0, sec 7.9.5.2. > + */ > +static const struct dwc_pcie_vsec_id dwc_pcie_vsec_ids[] = { > + { .vendor_id = PCI_VENDOR_ID_ALIBABA, > + .vsec_id = 0x02, .vsec_rev = 0x4 }, > + { .vendor_id = PCI_VENDOR_ID_AMPERE, > + .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 }, > + {} /* terminator */ This should go into the common include file as I proposed in my series: https://lore.kernel.org/linux-pci/20250218-pcie-qcom-ptm-v1-1-16d7e480d73e@linaro.org/ > +}; > + > +/** > + * struct dwc_pcie_rasdes_info - Stores controller common information > + * @ras_cap_offset: RAS DES vendor specific extended capability offset > + * @reg_lock: Mutex used for RASDES shadow event registers If this is not used by all register accesses, please rename it as such. Like, reg_event_lock. > + * > + * 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_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 off = 0; Not required. See below. > + 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) > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "Lane Detected\n"); I don't understand why you want to add 'off' which was initialized to 0. Also this just prints single string. > + else > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "Lane Undetected\n"); > + > + return simple_read_from_buffer(buf, count, ppos, debugfs_buf, off); > +} > + > +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 off = 0; > + u32 val; > + > + val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG); > + val = FIELD_GET(PIPE_RXVALID, val); > + if (val) > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "RX Valid\n"); Same here. > + else > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "RX Invalid\n"); > + > + return simple_read_from_buffer(buf, count, ppos, debugfs_buf, off); > +} > + > +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_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; > + const struct dwc_pcie_vsec_id *vid; > + struct device *dev = pci->dev; > + int ras_cap; > + > + for (vid = dwc_pcie_vsec_ids; vid->vendor_id; vid++) { > + ras_cap = dw_pcie_find_vsec_capability(pci, vid->vendor_id, > + vid->vsec_id); > + if (ras_cap) > + break; > + } > + if (!ras_cap) { > + dev_dbg(dev, "no rasdes capability available\n"); > + return -ENODEV; > + } This will also go inside a new API, dw_pcie_find_rasdes_capability(pci). > + > + 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); _debug prefix is not needed since the directory itself belongs to debugfs. > + > + mutex_init(&rasdes_info->reg_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) > +{ > + dwc_pcie_rasdes_debugfs_deinit(pci); > + debugfs_remove_recursive(pci->debugfs->debug_dir); > +} > + > +int 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 ret; > + > + /* 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); > + if (IS_ERR(dir)) > + return PTR_ERR(dir); debugfs creation is not supposed to fail. So you should remove the error check. > + > + debugfs = devm_kzalloc(dev, sizeof(*debugfs), GFP_KERNEL); > + if (!debugfs) > + return -ENOMEM; > + > + debugfs->debug_dir = dir; > + pci->debugfs = debugfs; > + ret = dwc_pcie_rasdes_debugfs_init(pci, dir); > + if (ret) > + dev_dbg(dev, "rasdes debugfs init failed\n"); RASDES > + > + return 0; > +} > diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c > index f3ac7d46a855..a87a714bb472 100644 > --- a/drivers/pci/controller/dwc/pcie-designware-ep.c > +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c > @@ -642,6 +642,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); > @@ -813,6 +814,10 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep *ep) > > dw_pcie_ep_init_non_sticky_registers(pci); > > + ret = dwc_pcie_debugfs_init(pci); > + if (ret) > + goto err_remove_edma; > + > return 0; > > err_remove_edma: > diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c > index d2291c3ceb8b..6b03ef7fd014 100644 > --- a/drivers/pci/controller/dwc/pcie-designware-host.c > +++ b/drivers/pci/controller/dwc/pcie-designware-host.c > @@ -524,6 +524,10 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) > if (ret) > goto err_remove_edma; > > + ret = dwc_pcie_debugfs_init(pci); > + if (ret) > + goto err_remove_edma; > + Why can't you move it to the end of the function? > if (!dw_pcie_link_up(pci)) { > ret = dw_pcie_start_link(pci); > if (ret) > @@ -571,6 +575,8 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp) > > dw_pcie_stop_link(pci); > > + dwc_pcie_debugfs_deinit(pci); > + This should be moved to the start of the function. - Mani
> -----Original Message----- > From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> > Sent: 18 February 2025 20:33 > To: Shradha Todi <shradha.t@samsung.com> > Cc: linux-kernel@vger.kernel.org; linux-pci@vger.kernel.org; lpieralisi@kernel.org; kw@linux.com; robh@kernel.org; > bhelgaas@google.com; jingoohan1@gmail.com; Jonathan.Cameron@Huawei.com; fan.ni@samsung.com; nifan.cxl@gmail.com; > a.manzanares@samsung.com; pankaj.dubey@samsung.com; cassel@kernel.org; 18255117159@163.com; > quic_nitegupt@quicinc.com; quic_krichai@quicinc.com; gost.dev@samsung.com > Subject: Re: [PATCH v6 2/4] Add debugfs based silicon debug support in DWC > > On Fri, Feb 14, 2025 at 04:20:05PM +0530, Shradha Todi wrote: > > Add support to provide silicon debug interface to userspace. This set > > of debug registers are part of the RASDES feature present in > > DesignWare PCIe controllers. > > > > Signed-off-by: Shradha Todi <shradha.t@samsung.com> > > --- > > Documentation/ABI/testing/debugfs-dwc-pcie | 13 ++ > > drivers/pci/controller/dwc/Kconfig | 10 + > > drivers/pci/controller/dwc/Makefile | 1 + > > .../controller/dwc/pcie-designware-debugfs.c | 207 ++++++++++++++++++ > > .../pci/controller/dwc/pcie-designware-ep.c | 5 + > > .../pci/controller/dwc/pcie-designware-host.c | 6 + > > drivers/pci/controller/dwc/pcie-designware.h | 20 ++ > > 7 files changed, 262 insertions(+) > > create mode 100644 Documentation/ABI/testing/debugfs-dwc-pcie > > create mode 100644 > > drivers/pci/controller/dwc/pcie-designware-debugfs.c > > > > diff --git a/Documentation/ABI/testing/debugfs-dwc-pcie > > b/Documentation/ABI/testing/debugfs-dwc-pcie > > new file mode 100644 > > index 000000000000..e8ed34e988ef > > --- /dev/null > > +++ b/Documentation/ABI/testing/debugfs-dwc-pcie > > @@ -0,0 +1,13 @@ > > +What: /sys/kernel/debug/dwc_pcie_<dev>/rasdes_debug/lane_detect > > +Date: Feburary 2025 > > Please align these fields The fields are already aligned in my patch (when I check in git), not sure why the mail misaligns it. Can you suggest how should I fix this? > > > +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: Feburary 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. > > diff --git a/drivers/pci/controller/dwc/Kconfig > > b/drivers/pci/controller/dwc/Kconfig > > index b6d6778b0698..48a10428a492 100644 > > --- a/drivers/pci/controller/dwc/Kconfig > > +++ b/drivers/pci/controller/dwc/Kconfig > > @@ -6,6 +6,16 @@ menu "DesignWare-based PCIe controllers" > > config PCIE_DW > > bool > > > > +config PCIE_DW_DEBUGFS > > + default y > > + depends on DEBUG_FS > > + depends on PCIE_DW_HOST || PCIE_DW_EP > > + bool "DWC PCIe debugfs entries" > > + help > > + Enables debugfs entries for the DW PCIe Controller. These entries > > + provide all debug features related to DW controller including the RAS > > + DES features to help in debug, error injection and statistical counters. > > + > > config PCIE_DW_HOST > > bool > > select PCIE_DW > > diff --git a/drivers/pci/controller/dwc/Makefile > > b/drivers/pci/controller/dwc/Makefile > > index a8308d9ea986..54565eedc52c 100644 > > --- a/drivers/pci/controller/dwc/Makefile > > +++ b/drivers/pci/controller/dwc/Makefile > > @@ -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 > > index 000000000000..fe799d36fa7f > > --- /dev/null > > +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c > > @@ -0,0 +1,207 @@ > > +// 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_vsec_id { > > + u16 vendor_id; > > + u16 vsec_id; > > + u8 vsec_rev; > > +}; > > + > > +/* > > + * VSEC IDs are allocated by the vendor, so a given ID may mean > > +different > > + * things to different vendors. See PCIe r6.0, sec 7.9.5.2. > > + */ > > +static const struct dwc_pcie_vsec_id dwc_pcie_vsec_ids[] = { > > + { .vendor_id = PCI_VENDOR_ID_ALIBABA, > > + .vsec_id = 0x02, .vsec_rev = 0x4 }, > > + { .vendor_id = PCI_VENDOR_ID_AMPERE, > > + .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 }, > > + {} /* terminator */ > > This should go into the common include file as I proposed in my series: > https://lore.kernel.org/linux-pci/20250218-pcie-qcom-ptm-v1-1-16d7e480d73e@linaro.org/ > > > +}; > > + > > +/** > > + * struct dwc_pcie_rasdes_info - Stores controller common information > > + * @ras_cap_offset: RAS DES vendor specific extended capability > > +offset > > + * @reg_lock: Mutex used for RASDES shadow event registers > > If this is not used by all register accesses, please rename it as such. Like, reg_event_lock. > Will fix in next version > > + * > > + * 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_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 off = 0; > > Not required. See below. > > > + 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) > > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "Lane > > +Detected\n"); > > I don't understand why you want to add 'off' which was initialized to 0. Also this just prints single string. > Okay will change it everywhere. > > + else > > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "Lane > > +Undetected\n"); > > + > > + return simple_read_from_buffer(buf, count, ppos, debugfs_buf, off); > > +} > > + > > +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 off = 0; > > + u32 val; > > + > > + val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG); > > + val = FIELD_GET(PIPE_RXVALID, val); > > + if (val) > > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "RX > > +Valid\n"); > > Same here. > > > + else > > + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "RX > > +Invalid\n"); > > + > > + return simple_read_from_buffer(buf, count, ppos, debugfs_buf, off); > > +} > > + > > +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_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; > > + const struct dwc_pcie_vsec_id *vid; > > + struct device *dev = pci->dev; > > + int ras_cap; > > + > > + for (vid = dwc_pcie_vsec_ids; vid->vendor_id; vid++) { > > + ras_cap = dw_pcie_find_vsec_capability(pci, vid->vendor_id, > > + vid->vsec_id); > > + if (ras_cap) > > + break; > > + } > > + if (!ras_cap) { > > + dev_dbg(dev, "no rasdes capability available\n"); > > + return -ENODEV; > > + } > > This will also go inside a new API, dw_pcie_find_rasdes_capability(pci). > Okay, are we planning to make a function for each VSEC? Or should we just pass the rasdes_vids to the dw_pcie_find_vsec_capability? > > + > > + 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); > > _debug prefix is not needed since the directory itself belongs to debugfs. > It's not for the debug in debugfs. So the DES features are Debug Error Injection Statistics. The debug here is for the "D" in DES. > > + > > + mutex_init(&rasdes_info->reg_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) { > > + dwc_pcie_rasdes_debugfs_deinit(pci); > > + debugfs_remove_recursive(pci->debugfs->debug_dir); > > +} > > + > > +int 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 ret; > > + > > + /* 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); > > + if (IS_ERR(dir)) > > + return PTR_ERR(dir); > > debugfs creation is not supposed to fail. So you should remove the error check. > There was no error check until v3. Got a comment from Jonathan in v3: "Check for errors in all these." I think he wanted to add in all the debugfs creations but I just added in the topmost directory. I checked that error will be returned in case someone turns off debugfs mounting as early param. So, if the first directory gets made, there would be no issues in subsequent subdirectories. > > + > > + debugfs = devm_kzalloc(dev, sizeof(*debugfs), GFP_KERNEL); > > + if (!debugfs) > > + return -ENOMEM; > > + > > + debugfs->debug_dir = dir; > > + pci->debugfs = debugfs; > > + ret = dwc_pcie_rasdes_debugfs_init(pci, dir); > > + if (ret) > > + dev_dbg(dev, "rasdes debugfs init failed\n"); > > RASDES > > > + > > + return 0; > > +} > > diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c > > b/drivers/pci/controller/dwc/pcie-designware-ep.c > > index f3ac7d46a855..a87a714bb472 100644 > > --- a/drivers/pci/controller/dwc/pcie-designware-ep.c > > +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c > > @@ -642,6 +642,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); > > @@ -813,6 +814,10 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep > > *ep) > > > > dw_pcie_ep_init_non_sticky_registers(pci); > > > > + ret = dwc_pcie_debugfs_init(pci); > > + if (ret) > > + goto err_remove_edma; > > + > > return 0; > > > > err_remove_edma: > > diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c > > b/drivers/pci/controller/dwc/pcie-designware-host.c > > index d2291c3ceb8b..6b03ef7fd014 100644 > > --- a/drivers/pci/controller/dwc/pcie-designware-host.c > > +++ b/drivers/pci/controller/dwc/pcie-designware-host.c > > @@ -524,6 +524,10 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) > > if (ret) > > goto err_remove_edma; > > > > + ret = dwc_pcie_debugfs_init(pci); > > + if (ret) > > + goto err_remove_edma; > > + > > Why can't you move it to the end of the function? So the debugfs entries record certain debug values that might be useful to read in case link does not come up. Therefore, I'm adding it before starting link up so that users can read it in case some failure occurs in further steps. > > > if (!dw_pcie_link_up(pci)) { > > ret = dw_pcie_start_link(pci); > > if (ret) > > @@ -571,6 +575,8 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp) > > > > dw_pcie_stop_link(pci); > > > > + dwc_pcie_debugfs_deinit(pci); > > + > > This should be moved to the start of the function. > > - Mani > > -- > மணிவண்ணன் சதாசிவம்
On Thu, Feb 20, 2025 at 02:48:12PM +0530, Shradha Todi wrote: > > > > -----Original Message----- > > From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> > > Sent: 18 February 2025 20:33 > > To: Shradha Todi <shradha.t@samsung.com> > > Cc: linux-kernel@vger.kernel.org; linux-pci@vger.kernel.org; lpieralisi@kernel.org; kw@linux.com; robh@kernel.org; > > bhelgaas@google.com; jingoohan1@gmail.com; Jonathan.Cameron@Huawei.com; fan.ni@samsung.com; nifan.cxl@gmail.com; > > a.manzanares@samsung.com; pankaj.dubey@samsung.com; cassel@kernel.org; 18255117159@163.com; > > quic_nitegupt@quicinc.com; quic_krichai@quicinc.com; gost.dev@samsung.com > > Subject: Re: [PATCH v6 2/4] Add debugfs based silicon debug support in DWC > > > > On Fri, Feb 14, 2025 at 04:20:05PM +0530, Shradha Todi wrote: > > > Add support to provide silicon debug interface to userspace. This set > > > of debug registers are part of the RASDES feature present in > > > DesignWare PCIe controllers. > > > > > > Signed-off-by: Shradha Todi <shradha.t@samsung.com> > > > --- > > > Documentation/ABI/testing/debugfs-dwc-pcie | 13 ++ > > > drivers/pci/controller/dwc/Kconfig | 10 + > > > drivers/pci/controller/dwc/Makefile | 1 + > > > .../controller/dwc/pcie-designware-debugfs.c | 207 ++++++++++++++++++ > > > .../pci/controller/dwc/pcie-designware-ep.c | 5 + > > > .../pci/controller/dwc/pcie-designware-host.c | 6 + > > > drivers/pci/controller/dwc/pcie-designware.h | 20 ++ > > > 7 files changed, 262 insertions(+) > > > create mode 100644 Documentation/ABI/testing/debugfs-dwc-pcie > > > create mode 100644 > > > drivers/pci/controller/dwc/pcie-designware-debugfs.c > > > > > > diff --git a/Documentation/ABI/testing/debugfs-dwc-pcie > > > b/Documentation/ABI/testing/debugfs-dwc-pcie > > > new file mode 100644 > > > index 000000000000..e8ed34e988ef > > > --- /dev/null > > > +++ b/Documentation/ABI/testing/debugfs-dwc-pcie > > > @@ -0,0 +1,13 @@ > > > +What: /sys/kernel/debug/dwc_pcie_<dev>/rasdes_debug/lane_detect > > > +Date: Feburary 2025 > > > > Please align these fields > > The fields are already aligned in my patch (when I check in git), not sure why the mail misaligns it. Can you suggest how > should I fix this? > You should consistently use tabs or space after colon throughout the file. Don't mix them. > > > > > +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: Feburary 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. [...] > > > +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; > > > + const struct dwc_pcie_vsec_id *vid; > > > + struct device *dev = pci->dev; > > > + int ras_cap; > > > + > > > + for (vid = dwc_pcie_vsec_ids; vid->vendor_id; vid++) { > > > + ras_cap = dw_pcie_find_vsec_capability(pci, vid->vendor_id, > > > + vid->vsec_id); > > > + if (ras_cap) > > > + break; > > > + } > > > + if (!ras_cap) { > > > + dev_dbg(dev, "no rasdes capability available\n"); > > > + return -ENODEV; > > > + } > > > > This will also go inside a new API, dw_pcie_find_rasdes_capability(pci). > > > > Okay, are we planning to make a function for each VSEC? Or should we just pass the rasdes_vids to the > dw_pcie_find_vsec_capability? > dw_pcie_find_vsec_capability() is a static function in my series. We should add separate function for each VSEC. > > > + > > > + 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); > > > > _debug prefix is not needed since the directory itself belongs to debugfs. > > > > It's not for the debug in debugfs. So the DES features are > Debug > Error Injection > Statistics. > The debug here is for the "D" in DES. > Sorry, I missed it. > > > + > > > + mutex_init(&rasdes_info->reg_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) { > > > + dwc_pcie_rasdes_debugfs_deinit(pci); > > > + debugfs_remove_recursive(pci->debugfs->debug_dir); > > > +} > > > + > > > +int 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 ret; > > > + > > > + /* 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); > > > + if (IS_ERR(dir)) > > > + return PTR_ERR(dir); > > > > debugfs creation is not supposed to fail. So you should remove the error check. > > > > There was no error check until v3. Got a comment from Jonathan in v3: > "Check for errors in all these." > I think he wanted to add in all the debugfs creations but I just added in the topmost directory. > I checked that error will be returned in case someone turns off debugfs mounting as early param. > So, if the first directory gets made, there would be no issues in subsequent subdirectories. > Please see the Kdoc comments for this API: * NOTE: it's expected that most callers should _ignore_ the errors returned * by this function. Other debugfs functions handle the fact that the "dentry" * passed to them could be an error and they don't crash in that case. * Drivers should generally work fine even if debugfs fails to init anyway. > > > + > > > + debugfs = devm_kzalloc(dev, sizeof(*debugfs), GFP_KERNEL); > > > + if (!debugfs) > > > + return -ENOMEM; > > > + > > > + debugfs->debug_dir = dir; > > > + pci->debugfs = debugfs; > > > + ret = dwc_pcie_rasdes_debugfs_init(pci, dir); > > > + if (ret) > > > + dev_dbg(dev, "rasdes debugfs init failed\n"); > > > > RASDES > > > > > + > > > + return 0; > > > +} > > > diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c > > > b/drivers/pci/controller/dwc/pcie-designware-ep.c > > > index f3ac7d46a855..a87a714bb472 100644 > > > --- a/drivers/pci/controller/dwc/pcie-designware-ep.c > > > +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c > > > @@ -642,6 +642,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); > > > @@ -813,6 +814,10 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep > > > *ep) > > > > > > dw_pcie_ep_init_non_sticky_registers(pci); > > > > > > + ret = dwc_pcie_debugfs_init(pci); > > > + if (ret) > > > + goto err_remove_edma; > > > + > > > return 0; > > > > > > err_remove_edma: > > > diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c > > > b/drivers/pci/controller/dwc/pcie-designware-host.c > > > index d2291c3ceb8b..6b03ef7fd014 100644 > > > --- a/drivers/pci/controller/dwc/pcie-designware-host.c > > > +++ b/drivers/pci/controller/dwc/pcie-designware-host.c > > > @@ -524,6 +524,10 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) > > > if (ret) > > > goto err_remove_edma; > > > > > > + ret = dwc_pcie_debugfs_init(pci); > > > + if (ret) > > > + goto err_remove_edma; > > > + > > > > Why can't you move it to the end of the function? > > So the debugfs entries record certain debug values that might be useful to read > in case link does not come up. Therefore, I'm adding it before starting link up so that users can > read it in case some failure occurs in further steps. > I don't understand this. Even if you move it to the end of the function, users can still read the attributes and learn what happended with the link. We don't fail if the link doesn't come up. - Mani
diff --git a/Documentation/ABI/testing/debugfs-dwc-pcie b/Documentation/ABI/testing/debugfs-dwc-pcie new file mode 100644 index 000000000000..e8ed34e988ef --- /dev/null +++ b/Documentation/ABI/testing/debugfs-dwc-pcie @@ -0,0 +1,13 @@ +What: /sys/kernel/debug/dwc_pcie_<dev>/rasdes_debug/lane_detect +Date: Feburary 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: Feburary 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. diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index b6d6778b0698..48a10428a492 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -6,6 +6,16 @@ menu "DesignWare-based PCIe controllers" config PCIE_DW bool +config PCIE_DW_DEBUGFS + default y + depends on DEBUG_FS + depends on PCIE_DW_HOST || PCIE_DW_EP + bool "DWC PCIe debugfs entries" + help + Enables debugfs entries for the DW PCIe Controller. These entries + provide all debug features related to DW controller including the RAS + DES features to help in debug, error injection and statistical counters. + config PCIE_DW_HOST bool select PCIE_DW diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index a8308d9ea986..54565eedc52c 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -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 index 000000000000..fe799d36fa7f --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -0,0 +1,207 @@ +// 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_vsec_id { + u16 vendor_id; + u16 vsec_id; + u8 vsec_rev; +}; + +/* + * VSEC IDs are allocated by the vendor, so a given ID may mean different + * things to different vendors. See PCIe r6.0, sec 7.9.5.2. + */ +static const struct dwc_pcie_vsec_id dwc_pcie_vsec_ids[] = { + { .vendor_id = PCI_VENDOR_ID_ALIBABA, + .vsec_id = 0x02, .vsec_rev = 0x4 }, + { .vendor_id = PCI_VENDOR_ID_AMPERE, + .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 }, + {} /* terminator */ +}; + +/** + * struct dwc_pcie_rasdes_info - Stores controller common information + * @ras_cap_offset: RAS DES vendor specific extended capability offset + * @reg_lock: Mutex used for RASDES 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_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 off = 0; + 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) + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "Lane Detected\n"); + else + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "Lane Undetected\n"); + + return simple_read_from_buffer(buf, count, ppos, debugfs_buf, off); +} + +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 off = 0; + u32 val; + + val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG); + val = FIELD_GET(PIPE_RXVALID, val); + if (val) + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "RX Valid\n"); + else + off += scnprintf(debugfs_buf, DWC_DEBUGFS_BUF_MAX - off, "RX Invalid\n"); + + return simple_read_from_buffer(buf, count, ppos, debugfs_buf, off); +} + +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_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; + const struct dwc_pcie_vsec_id *vid; + struct device *dev = pci->dev; + int ras_cap; + + for (vid = dwc_pcie_vsec_ids; vid->vendor_id; vid++) { + ras_cap = dw_pcie_find_vsec_capability(pci, vid->vendor_id, + vid->vsec_id); + if (ras_cap) + break; + } + if (!ras_cap) { + dev_dbg(dev, "no rasdes capability available\n"); + return -ENODEV; + } + + 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_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) +{ + dwc_pcie_rasdes_debugfs_deinit(pci); + debugfs_remove_recursive(pci->debugfs->debug_dir); +} + +int 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 ret; + + /* 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); + if (IS_ERR(dir)) + return PTR_ERR(dir); + + debugfs = devm_kzalloc(dev, sizeof(*debugfs), GFP_KERNEL); + if (!debugfs) + return -ENOMEM; + + debugfs->debug_dir = dir; + pci->debugfs = debugfs; + ret = dwc_pcie_rasdes_debugfs_init(pci, dir); + if (ret) + dev_dbg(dev, "rasdes debugfs init failed\n"); + + return 0; +} diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index f3ac7d46a855..a87a714bb472 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -642,6 +642,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); @@ -813,6 +814,10 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep *ep) dw_pcie_ep_init_non_sticky_registers(pci); + ret = dwc_pcie_debugfs_init(pci); + if (ret) + goto err_remove_edma; + return 0; err_remove_edma: diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index d2291c3ceb8b..6b03ef7fd014 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -524,6 +524,10 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) if (ret) goto err_remove_edma; + ret = dwc_pcie_debugfs_init(pci); + if (ret) + goto err_remove_edma; + if (!dw_pcie_link_up(pci)) { ret = dw_pcie_start_link(pci); if (ret) @@ -571,6 +575,8 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp) dw_pcie_stop_link(pci); + dwc_pcie_debugfs_deinit(pci); + dw_pcie_edma_remove(pci); if (pp->has_msi_ctrl) diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 02e94bd9b042..8543b4f38d24 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -435,6 +435,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; @@ -463,6 +468,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) @@ -796,4 +802,18 @@ dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, u8 func_no) return NULL; } #endif + +#ifdef CONFIG_PCIE_DW_DEBUGFS +int dwc_pcie_debugfs_init(struct dw_pcie *pci); +void dwc_pcie_debugfs_deinit(struct dw_pcie *pci); +#else +static inline int dwc_pcie_debugfs_init(struct dw_pcie *pci) +{ + return 0; +} +static inline void dwc_pcie_debugfs_deinit(struct dw_pcie *pci) +{ +} +#endif + #endif /* _PCIE_DESIGNWARE_H */
Add support to provide silicon debug interface to userspace. This set of debug registers are part of the RASDES feature present in DesignWare PCIe controllers. Signed-off-by: Shradha Todi <shradha.t@samsung.com> --- Documentation/ABI/testing/debugfs-dwc-pcie | 13 ++ drivers/pci/controller/dwc/Kconfig | 10 + drivers/pci/controller/dwc/Makefile | 1 + .../controller/dwc/pcie-designware-debugfs.c | 207 ++++++++++++++++++ .../pci/controller/dwc/pcie-designware-ep.c | 5 + .../pci/controller/dwc/pcie-designware-host.c | 6 + drivers/pci/controller/dwc/pcie-designware.h | 20 ++ 7 files changed, 262 insertions(+) create mode 100644 Documentation/ABI/testing/debugfs-dwc-pcie create mode 100644 drivers/pci/controller/dwc/pcie-designware-debugfs.c