PCI: dwc: Add debugfs based Error Injection support for DWC

Add support to provide Error Injection interface to userspace.

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

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-5-shradha.t@samsung.com
[kwilczynski: commit log, tidy up code comments, update documentation,
change debugfs property name from "duplicate_dllp" to "duplicate_tlp"]
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
This commit is contained in:
Shradha Todi 2025-02-21 18:45:47 +05:30 committed by Krzysztof Wilczyński
parent 4fbfa17f9a
commit d20ee8e2db
No known key found for this signature in database
GPG Key ID: 7C64768D3DE334E7
2 changed files with 240 additions and 2 deletions

View File

@ -11,3 +11,77 @@ 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.
What: /sys/kernel/debug/dwc_pcie_<dev>/rasdes_err_inj/<error>
Date: February 2025
Contact: Shradha Todi <shradha.t@samsung.com>
Description: The "rasdes_err_inj" is a directory which can be used to inject
errors into the system. The possible errors that can be injected
are:
1) tx_lcrc - TLP LCRC error injection TX Path
2) b16_crc_dllp - 16b CRC error injection of ACK/NAK DLLP
3) b16_crc_upd_fc - 16b CRC error injection of Update-FC DLLP
4) tx_ecrc - TLP ECRC error injection TX Path
5) fcrc_tlp - TLP's FCRC error injection TX Path
6) parity_tsos - Parity error of TSOS
7) parity_skpos - Parity error on SKPOS
8) rx_lcrc - LCRC error injection RX Path
9) rx_ecrc - ECRC error injection RX Path
10) tlp_err_seq - TLPs SEQ# error
11) ack_nak_dllp_seq - DLLPS ACK/NAK SEQ# error
12) ack_nak_dllp - ACK/NAK DLLPs transmission block
13) upd_fc_dllp - UpdateFC DLLPs transmission block
14) nak_dllp - Always transmission for NAK DLLP
15) inv_sync_hdr_sym - Invert SYNC header
16) com_pad_ts1 - COM/PAD TS1 order set
17) com_pad_ts2 - COM/PAD TS2 order set
18) com_fts - COM/FTS FTS order set
19) com_idl - COM/IDL E-idle order set
20) end_edb - END/EDB symbol
21) stp_sdp - STP/SDP symbol
22) com_skp - COM/SKP SKP order set
23) posted_tlp_hdr - Posted TLP Header credit value control
24) non_post_tlp_hdr - Non-Posted TLP Header credit value control
25) cmpl_tlp_hdr - Completion TLP Header credit value control
26) posted_tlp_data - Posted TLP Data credit value control
27) non_post_tlp_data - Non-Posted TLP Data credit value control
28) cmpl_tlp_data - Completion TLP Data credit value control
29) duplicate_tlp - Generates duplicate TLPs
30) nullified_tlp - Generates Nullified TLPs
(WO) Write to the attribute will prepare controller to inject
the respective error in the next transmission of data.
Parameter required to write will change in the following ways:
- Errors 9 and 10 are sequence errors. The write command:
echo <count> <diff> > /sys/kernel/debug/dwc_pcie_<dev>/rasdes_err_inj/<error>
<count>
Number of errors to be injected
<diff>
The difference to add or subtract from natural
sequence number to generate sequence error.
Allowed range from -4095 to 4095
- Errors 23 to 28 are credit value error insertions. The write
command:
echo <count> <diff> <vc> > /sys/kernel/debug/dwc_pcie_<dev>/rasdes_err_inj/<error>
<count>
Number of errors to be injected
<diff>
The difference to add or subtract from UpdateFC
credit value. Allowed range from -4095 to 4095
<vc>
Target VC number
- All other errors. The write command:
echo <count> > /sys/kernel/debug/dwc_pcie_<dev>/rasdes_err_inj/<error>
<count>
Number of errors to be injected

View File

@ -17,6 +17,20 @@
#define PIPE_DETECT_LANE BIT(17)
#define LANE_SELECT GENMASK(3, 0)
#define ERR_INJ0_OFF 0x34
#define EINJ_VAL_DIFF GENMASK(28, 16)
#define EINJ_VC_NUM GENMASK(14, 12)
#define EINJ_TYPE_SHIFT 8
#define EINJ0_TYPE GENMASK(11, 8)
#define EINJ1_TYPE BIT(8)
#define EINJ2_TYPE GENMASK(9, 8)
#define EINJ3_TYPE GENMASK(10, 8)
#define EINJ4_TYPE GENMASK(10, 8)
#define EINJ5_TYPE BIT(8)
#define EINJ_COUNT GENMASK(7, 0)
#define ERR_INJ_ENABLE_REG 0x30
#define DWC_DEBUGFS_BUF_MAX 128
/**
@ -33,6 +47,74 @@ struct dwc_pcie_rasdes_info {
struct mutex reg_event_lock;
};
/**
* struct dwc_pcie_rasdes_priv - Stores file specific private data information
* @pci: Reference to the dw_pcie structure
* @idx: Index of specific file related information in array of structs
*
* All debugfs files will have this struct as its private data.
*/
struct dwc_pcie_rasdes_priv {
struct dw_pcie *pci;
int idx;
};
/**
* struct dwc_pcie_err_inj - Store details about each error injection
* supported by DWC RAS DES
* @name: Name of the error that can be injected
* @err_inj_group: Group number to which the error belongs. The value
* can range from 0 to 5
* @err_inj_type: Each group can have multiple types of error
*/
struct dwc_pcie_err_inj {
const char *name;
u32 err_inj_group;
u32 err_inj_type;
};
static const struct dwc_pcie_err_inj err_inj_list[] = {
{"tx_lcrc", 0x0, 0x0},
{"b16_crc_dllp", 0x0, 0x1},
{"b16_crc_upd_fc", 0x0, 0x2},
{"tx_ecrc", 0x0, 0x3},
{"fcrc_tlp", 0x0, 0x4},
{"parity_tsos", 0x0, 0x5},
{"parity_skpos", 0x0, 0x6},
{"rx_lcrc", 0x0, 0x8},
{"rx_ecrc", 0x0, 0xb},
{"tlp_err_seq", 0x1, 0x0},
{"ack_nak_dllp_seq", 0x1, 0x1},
{"ack_nak_dllp", 0x2, 0x0},
{"upd_fc_dllp", 0x2, 0x1},
{"nak_dllp", 0x2, 0x2},
{"inv_sync_hdr_sym", 0x3, 0x0},
{"com_pad_ts1", 0x3, 0x1},
{"com_pad_ts2", 0x3, 0x2},
{"com_fts", 0x3, 0x3},
{"com_idl", 0x3, 0x4},
{"end_edb", 0x3, 0x5},
{"stp_sdp", 0x3, 0x6},
{"com_skp", 0x3, 0x7},
{"posted_tlp_hdr", 0x4, 0x0},
{"non_post_tlp_hdr", 0x4, 0x1},
{"cmpl_tlp_hdr", 0x4, 0x2},
{"posted_tlp_data", 0x4, 0x4},
{"non_post_tlp_data", 0x4, 0x5},
{"cmpl_tlp_data", 0x4, 0x6},
{"duplicate_tlp", 0x5, 0x0},
{"nullified_tlp", 0x5, 0x1},
};
static const u32 err_inj_type_mask[] = {
EINJ0_TYPE,
EINJ1_TYPE,
EINJ2_TYPE,
EINJ3_TYPE,
EINJ4_TYPE,
EINJ5_TYPE,
};
static ssize_t lane_detect_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
@ -96,6 +178,64 @@ static ssize_t rx_valid_write(struct file *file, const char __user *buf,
return lane_detect_write(file, buf, count, ppos);
}
static ssize_t err_inj_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct dwc_pcie_rasdes_priv *pdata = file->private_data;
struct dw_pcie *pci = pdata->pci;
struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
u32 val, counter, vc_num, err_group, type_mask;
int val_diff = 0;
char *kern_buf;
err_group = err_inj_list[pdata->idx].err_inj_group;
type_mask = err_inj_type_mask[err_group];
kern_buf = memdup_user_nul(buf, count);
if (IS_ERR(kern_buf))
return PTR_ERR(kern_buf);
if (err_group == 4) {
val = sscanf(kern_buf, "%u %d %u", &counter, &val_diff, &vc_num);
if ((val != 3) || (val_diff < -4095 || val_diff > 4095)) {
kfree(kern_buf);
return -EINVAL;
}
} else if (err_group == 1) {
val = sscanf(kern_buf, "%u %d", &counter, &val_diff);
if ((val != 2) || (val_diff < -4095 || val_diff > 4095)) {
kfree(kern_buf);
return -EINVAL;
}
} else {
val = kstrtou32(kern_buf, 0, &counter);
if (val) {
kfree(kern_buf);
return val;
}
}
val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + ERR_INJ0_OFF + (0x4 * err_group));
val &= ~(type_mask | EINJ_COUNT);
val |= ((err_inj_list[pdata->idx].err_inj_type << EINJ_TYPE_SHIFT) & type_mask);
val |= FIELD_PREP(EINJ_COUNT, counter);
if (err_group == 1 || err_group == 4) {
val &= ~(EINJ_VAL_DIFF);
val |= FIELD_PREP(EINJ_VAL_DIFF, val_diff);
}
if (err_group == 4) {
val &= ~(EINJ_VC_NUM);
val |= FIELD_PREP(EINJ_VC_NUM, vc_num);
}
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + ERR_INJ0_OFF + (0x4 * err_group), val);
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + ERR_INJ_ENABLE_REG, (0x1 << err_group));
kfree(kern_buf);
return count;
}
#define dwc_debugfs_create(name) \
debugfs_create_file(#name, 0644, rasdes_debug, pci, \
&dbg_ ## name ## _fops)
@ -110,6 +250,11 @@ static const struct file_operations dbg_ ## name ## _fops = { \
DWC_DEBUGFS_FOPS(lane_detect);
DWC_DEBUGFS_FOPS(rx_valid);
static const struct file_operations dwc_pcie_err_inj_ops = {
.open = simple_open,
.write = err_inj_write,
};
static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci)
{
struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
@ -119,10 +264,11 @@ static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci)
static int dwc_pcie_rasdes_debugfs_init(struct dw_pcie *pci, struct dentry *dir)
{
struct dentry *rasdes_debug;
struct dentry *rasdes_debug, *rasdes_err_inj;
struct dwc_pcie_rasdes_info *rasdes_info;
struct dwc_pcie_rasdes_priv *priv_tmp;
struct device *dev = pci->dev;
int ras_cap;
int ras_cap, i, ret;
/*
* If a given SoC has no RAS DES capability, the following call is
@ -141,6 +287,7 @@ static int dwc_pcie_rasdes_debugfs_init(struct dw_pcie *pci, struct dentry *dir)
/* Create subdirectories for Debug, Error Injection, Statistics. */
rasdes_debug = debugfs_create_dir("rasdes_debug", dir);
rasdes_err_inj = debugfs_create_dir("rasdes_err_inj", dir);
mutex_init(&rasdes_info->reg_event_lock);
rasdes_info->ras_cap_offset = ras_cap;
@ -150,7 +297,24 @@ static int dwc_pcie_rasdes_debugfs_init(struct dw_pcie *pci, struct dentry *dir)
dwc_debugfs_create(lane_detect);
dwc_debugfs_create(rx_valid);
/* Create debugfs files for Error Injection subdirectory. */
for (i = 0; i < ARRAY_SIZE(err_inj_list); i++) {
priv_tmp = devm_kzalloc(dev, sizeof(*priv_tmp), GFP_KERNEL);
if (!priv_tmp) {
ret = -ENOMEM;
goto err_deinit;
}
priv_tmp->idx = i;
priv_tmp->pci = pci;
debugfs_create_file(err_inj_list[i].name, 0200, rasdes_err_inj, priv_tmp,
&dwc_pcie_err_inj_ops);
}
return 0;
err_deinit:
dwc_pcie_rasdes_debugfs_deinit(pci);
return ret;
}
void dwc_pcie_debugfs_deinit(struct dw_pcie *pci)