Hi all,

As it's a new tracing device for PCIe traffic, any comments about
the driver or the user interface is appreciated.

Thanks.


On 2020/6/13 17:32, Yicong Yang wrote:
> HiSilicon PCIe tune and trace device(PTT) is a PCIe Root Complex
> integrated Endpoint(RCiEP) device, providing the capability
> to dynamically monitor and tune the PCIe traffic parameters(tune),
> and trace the TLP headers to the memory(trace).
>
> Add the driver for the device to enable its functions. The driver
> will create debugfs directory for each PTT device, and users can
> operate the device through the files under its directory.
>
> RFC:
> - The hardware interface is not yet finalized.
> - The interface to the users is through debugfs, and the usage will
>   be further illustrated in the document.
> - The driver is intended to be put under drivers/hwtracing, where
>   we think best match the device's function.
>
> Signed-off-by: Yicong Yang <[email protected]>
> ---
>  Documentation/trace/hisi-ptt.rst       |  272 ++++++++
>  drivers/hwtracing/Kconfig              |    2 +
>  drivers/hwtracing/hisilicon/Kconfig    |    8 +
>  drivers/hwtracing/hisilicon/Makefile   |    2 +
>  drivers/hwtracing/hisilicon/hisi_ptt.c | 1172 
> ++++++++++++++++++++++++++++++++
>  5 files changed, 1456 insertions(+)
>  create mode 100644 Documentation/trace/hisi-ptt.rst
>  create mode 100644 drivers/hwtracing/hisilicon/Kconfig
>  create mode 100644 drivers/hwtracing/hisilicon/Makefile
>  create mode 100644 drivers/hwtracing/hisilicon/hisi_ptt.c
>
> diff --git a/Documentation/trace/hisi-ptt.rst 
> b/Documentation/trace/hisi-ptt.rst
> new file mode 100644
> index 0000000..c99fbf9
> --- /dev/null
> +++ b/Documentation/trace/hisi-ptt.rst
> @@ -0,0 +1,272 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +======================================
> +HiSilicon PCIe Tune and Trace device
> +======================================
> +
> +Introduction
> +============
> +
> +HiSilicon PCIe tune and trace device(PTT) is a PCIe Root Complex
> +integrated Endpoint(RCiEP) device, providing the capability
> +to dynamically monitor and tune the PCIe link's events(tune),
> +and trace the TLP headers to the memory(trace). The two functions
> +are inpendent, but is recommended to use them together to analyze
> +and enhance the PCIe link's performance.
> +
> +On Hip09, the PCIe root complex is composed of several PCIe cores.
> +And each core is composed of several root ports, RCiEPs, and one
> +PTT device, like below. The PTT device is capable of tuning and
> +tracing the link on and downstream the PCIe core.
> +::
> +          +--------------Core 0-------+
> +          |       |       [   PTT   ] |
> +          |       |       [Root Port]---[Endpoint]
> +          |       |       [Root Port]---[Endpoint]
> +          |       |       [Root Port]---[Endpoint]
> +    Root Complex  |------Core 1-------+
> +          |       |       [   PTT   ] |
> +          |       |       [Root Port]---[ Switch ]---[Endpoint]
> +          |       |       [Root Port]---[Endpoint] `-[Endpoint]
> +          |       |       [Root Port]---[Endpoint]
> +          +---------------------------+
> +
> +The PTT device driver cannot be loaded if debugfs is not mounted.
> +Each PTT device will be presented under /sys/kernel/debugfs/hisi_ptt
> +as its root directory, with name of its BDF number.
> +::
> +
> +    /sys/kernel/debug/hisi_ptt/<domain>:<bus>:<device>.<function>
> +
> +Tune
> +====
> +
> +PTT tune is designed for monitoring and adjusting PCIe link 
> parameters(events).
> +Currently we support events 4 classes. The scope of the events
> +covers the PCIe core with which the PTT device belongs to.
> +
> +Each event is presented as a file under $(PTT root dir)/$(BDF)/tune, and
> +mostly this will be a simple open/read/write/close cycle to tune
> +the event.
> +::
> +    $ cd /sys/kernel/debug/hisi_ptt/$(BDF)/tune
> +    $ ls
> +    buf_rx_cpld buf_rx_pd   dllp_link_ack_freq  link_credit_rx_cplh
> +    link_credit_rx_ph       qos_tx_dp           qos_tx_dp
> +    $ cat qos_tx_dp
> +    100
> +    $ echo 50 > qos_tx_dp
> +    $ cat qos_tx_dp
> +    50
> +
> +Current value(numerical value) of the event can be get by simply
> +read the file, and write the desired value to the file to tune.
> +Tune multiple events at the same time is not permitted, which means
> +you cannot read or write more than one tune file at one time.
> +
> +1. Link credit control
> +----------------------
> +
> +Following files are provided for tune the link credit events of the PCIe 
> core.
> +PCIe link uses credit to control the flow, refer to the PCIe Spec for further
> +information.
> +
> +- link_credit_rx_nph: rx non-posted request headers' credit
> +- link_credit_rx_npd: rx non-posted request data payload's credit
> +- link_credit_rx_ph: rx posted request headers' credit
> +- link_credit_rx_pd: rx posted request data payload's credit
> +- link_credit_rx_cplh: rx completion headers' credit
> +- link_credit_rx_cpld: rx completion data payload's credit
> +
> +Note that the event value is not accurate but a probable one to indicate
> +the level of each event, for example, perhaps 100 for high level,
> +50 for median and 0 for low.
> +
> +2. Link DLLP control
> +--------------------
> +
> +Following files are provided for tune the link events of DLLP of the PCIe 
> core.
> +
> +- dllp_link_ack_freq: frequency of DLLP ACKs
> +- dllp_link_updatefc_freq: frequency of DLLP flow control updates
> +- dllp_link_ssc: spread spectrum control of DLLP link
> +
> +Note that the event value just indicates a probable level, but not
> +accurate.
> +
> +3. Buffer control
> +-----------------
> +
> +Following files are provided for tune the rx/tx buffer depth of the PCIe 
> core.
> +
> +- buf_tx_header: buffer depth for tx packets headers
> +- buf_tx_data: buffer depth for tx packets data payloads
> +- buf_rx_ph: buffer depth for rx posted request packets headers
> +- buf_rx_pd: buffer depth for rx posted request packets data payloads
> +- buf_rx_nph: buffer depth for rx non-posted request packets headers
> +- buf_rx_npd: buffer depth for rx non-posted request packets data payloads
> +- buf_rx_cplh: buffer depth for rx completion packets headers
> +- buf_rx_cpld: buffer depth for rx completion packets data payloads
> +
> +Note that the event value just indicates a probable level, but not
> +accurate.
> +
> +4. Data path QoS control
> +------------------------
> +
> +Following files are provided for tune the QoS of the data path of the PCIe 
> core.
> +
> +- qos_tx_dp: QoS for tx data path
> +- qos_rx_dp: QoS for rx data path
> +
> +Note that the event value just indicates a probable level, but not
> +accurate.
> +
> +Trace
> +=====
> +
> +PTT trace is designed for dumping the TLP headers to the memory, which
> +can be used to analyze the transactions and usage condition of the PCIe
> +Link. You can choose to trace the headers either by its requester ID,
> +or the headers from the link downstream certain root ports, which are
> +on the same core of PTT device. It's also support to trace the headers
> +of certain type and of certain direction.
> +
> +In order to start trace, you need to configure the parameters first.
> +The parameters files is provided under $(PTT root dir)/$(BDF)/trace.
> +::
> +    $ cd /sys/kernel/debug/hisi_ptt/$(BDF)/trace
> +    $ ls
> +    free_buffer     filter      buflet_nums     buflet_size
> +    direction       type        data            trace_on
> +
> +1. filter
> +---------
> +
> +You can configure the filter of TLP headers through the file. The filter
> +is provided as BDF numbers of either root port or subordinates, which
> +belong to the same PCIe core. You can get the filters available and
> +currently configure by read the file, and write the desired BDF to the
> +file to set the filters. The default filter is the first root port on
> +the core, and write invalid BDF(not in the available list) will return
> +a failure.
> +::
> +    $ echo 0000:80:04.0 > filter
> +    $ cat filter
> +    0000:80:00.0    [0000:80:04.0]  0000:81:00.0    0000:81:00.1   
> 0000:82:00.0
> +
> +2. type
> +-------
> +
> +You can trace the TLP headers of certain types by configure the file.
> +Read the file will get available types and current setting, and write
> +the desired type to the file to configure. The default type is
> +`posted_request` and write types not in the available list will return
> +a failure.
> +::
> +    $ echo completion > type
> +    $ cat type
> +    posted_request  non-posted_request  [completion]    all
> +
> +3. direction
> +------------
> +
> +You can trace the TLP headers from certain direction, which is relative
> +to the root port or the PCIe core. Read the file to get available
> +directions and current configurition, and write the desired direction
> +to configure. The default value is `rx` and any invalid direction will
> +return a failure. Note `rxtx_no_dma_p2p` means the headers of both
> +directions, but not include P2P DMA access.
> +::
> +    $ echo rxtx > direction
> +    $ cat direction
> +    rx  tx  [rxtx]  rxtx_no_dma_p2p
> +
> +4. buflet_size
> +--------------
> +
> +The traced TLP headers will be written to the memory allocated
> +by the driver. The hardware accept 4 DMA address with same size,
> +and write the buflet sequetially like below. If DMA addr 3 is
> +finished and the trace is still on, it will return to addr 0.
> +Driver will allocated each DMA buffer (we call it buflet) and
> +swap a preallocated one if it has been finished.
> +::
> +    +->[DMA addr 0]->[DMA addr 1]->[DMA addr 2]->[DMA addr 3]-+
> +    +---------------------------------------------------------+
> +
> +You should both configure the buflet_size and buflet_nums to
> +configure the `trace buffer` to receive the TLP headers. The
> +total trace buffer size is buflet_size * buflet_nums. Note
> +that the trace buffer will not be allocated immediately after you
> +configure the parameters, but will be allocated right before
> +the trace starts.
> +
> +This file configures the buflet size. Read the file will get
> +available buflet size and size set currently, write the desired
> +size to the file to configure. The default size is 2 MiB and any
> +invalid size written will return a failure.
> +::
> +    $ cat buflet_size
> +    [2 MiB]     4 MiB     6 MiB     8 MiB     10 MiB
> +    $ echo 8 > buflet_size
> +    $ cat buflet_size
> +    2 MiB     4 MiB     6 MiB     [8 MiB]     10 MiB
> +
> +5. buflet_nums
> +--------------
> +
> +You can write the desired buflet counts to the file to configure,
> +and read the file to get current buflet counts. The default
> +value is 64. And any positive value is valid. Note that big value
> +may lead to DMA memory allocation failure, and you will not be
> +able to start tracing. If it happens, you should consider adjusting
> +buflet_nums or buflet_size.
> +::
> +    $ cat buflet_nums
> +    64
> +    $ echo 128 > buflet_nums
> +    $ cat buflet_nums
> +    128
> +
> +6. data
> +-------
> +
> +The file to access the traced data. You can read the file to get the
> +binary blob of traced TLP headers. The format of the headers is
> +4 Dword length and is just as defined by the PCIe Spec r4.0,
> +Sec 2.2.4.1, or 8 Dword length with additional 4 Dword extra
> +information.
> +
> +echo "" > data will free all the trace buffers allocated as well as
> +the traced datas.
> +
> +7. trace_on
> +-----------
> +
> +Start or end the trace by simple writing to the file, and monitor the
> +trace status by reading the file.
> +::
> +    $ echo 1 > trace_on     # start trace
> +    $ cat trace_on          # get the trace status
> +    1
> +    $ echo 0 > trace_on     # manually end trace
> +
> +The read value of the trace_on will be auto cleared if the buffer
> +allocated is full. 1 indicates the trace is running and 0 for
> +stopped. Write any non-zero value to the file can start trace.
> +
> +8. free_buffer
> +--------------
> +
> +File to indicate the trace buffer status and to manually free the
> +trace buffer. The read value of 1 indicates the trace buffer has
> +been allocated and exists in the memory, while 0 indicates there
> +is no buffer allocated. Write 1 to the file to free the trace
> +buffer as well as the traced datas.
> +::
> +    $ cat free_buffer
> +    1                       # indicate the buffer exists
> +    $ echo 1 > free_buffer  # free the trace buffer
> +    $ cat free_buffer
> +    0
> diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig
> index 1308583..e3796b1 100644
> --- a/drivers/hwtracing/Kconfig
> +++ b/drivers/hwtracing/Kconfig
> @@ -5,4 +5,6 @@ source "drivers/hwtracing/stm/Kconfig"
>  
>  source "drivers/hwtracing/intel_th/Kconfig"
>  
> +source "drivers/hwtracing/hisilicon/Kconfig"
> +
>  endmenu
> diff --git a/drivers/hwtracing/hisilicon/Kconfig 
> b/drivers/hwtracing/hisilicon/Kconfig
> new file mode 100644
> index 0000000..95e91b9
> --- /dev/null
> +++ b/drivers/hwtracing/hisilicon/Kconfig
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config HISI_PTT
> +     tristate "HiSilicon PCIe Tune and Trace Device"
> +     depends on PCI && HAS_DMA && HAS_IOMEM
> +     help
> +       HiSilicon PCIe Tune and Trace Device exist as a PCIe iEP
> +       device, provides support for PCIe traffic tuning and
> +       tracing TLP headers to the memory.
> diff --git a/drivers/hwtracing/hisilicon/Makefile 
> b/drivers/hwtracing/hisilicon/Makefile
> new file mode 100644
> index 0000000..908c09a
> --- /dev/null
> +++ b/drivers/hwtracing/hisilicon/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0
> +obj-$(CONFIG_HISI_PTT) += hisi_ptt.o
> diff --git a/drivers/hwtracing/hisilicon/hisi_ptt.c 
> b/drivers/hwtracing/hisilicon/hisi_ptt.c
> new file mode 100644
> index 0000000..c58a3cc
> --- /dev/null
> +++ b/drivers/hwtracing/hisilicon/hisi_ptt.c
> @@ -0,0 +1,1172 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for HiSilicon PCIe tune and trace device
> + *
> + * Copyright (c) 2020 HiSilicon Limited.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/seq_file.h>
> +#include <linux/pci.h>
> +
> +#define HISI_PTT_CTRL_STR_LEN        40
> +#define HISI_PTT_DEFAULT_TRACE_BUF_CNT       64
> +
> +#define HISI_PTT_RESET_WAIT_MS 1000UL
> +
> +#define HISI_PTT_IRQ_NUMS    1
> +#define HISI_PTT_DMA_IRQ     0
> +#define HISI_PTT_DMA_NUMS    4
> +
> +#define HISI_PTT_TUNING_CTRL         0x0380
> +#define   HISI_PTT_TUNING_CTRL_CODE  GENMASK(3, 0)
> +#define   HISI_PTT_TUNING_CTRL_EN    GENMASK(23, 16)
> +#define HISI_PTT_TUNING_DATA         0x0384
> +#define   HISI_PTT_TUNING_DATA_VAL   GENMASK(15, 0)
> +#define HISI_PTT_TRACE_ADDR_SIZE     0x0400
> +#define HISI_PTT_TRACE_ADDR_BASE_LO_0        0x0410
> +#define HISI_PTT_TRACE_ADDR_BASE_HI_0        0x0414
> +#define HISI_PTT_TRACE_CTRL          0x0450
> +#define   HISI_PTT_TRACE_CTRL_EN     BIT(0)
> +#define   HISI_PTT_TRACE_CTRL_RST    BIT(1)
> +#define   HISI_PTT_TRACE_CTRL_RXTX_SEL       GENMASK(3, 2)
> +#define   HISI_PTT_TRACE_CTRL_TYPE_SEL       GENMASK(7, 4)
> +#define   HISI_PTT_TRACE_CTRL_DATA_FORMAT    BIT(14)
> +#define   HISI_PTT_TRACE_CTRL_FILTER_MODE    BIT(15)
> +#define   HISI_PTT_TRACE_CTRL_TARGET_SEL     GENMASK(31, 16)
> +#define HISI_PTT_TRACE_INT_STAT              0x0490
> +#define   HISI_PTT_TRACE_INT_STAT_MASK       GENMASK(3, 0)
> +#define HISI_PTT_TRACE_WR_STS                0x04a0
> +#define   HISI_PTT_TRACE_WR_STS_WRITE        GENMASK(27, 0)
> +#define   HISI_PTT_TRACE_WR_STS_BUFFER       GENMASK(29, 28)
> +#define HISI_PTT_TRACE_STS           0x04b0
> +#define   HISI_PTT_TRACE_IDLE                BIT(0)
> +#define HISI_PTT_MAILBOX_0           0x07e0
> +
> +static struct dentry *hisi_ptt_debugfs_root;
> +
> +struct event_desc {
> +     const char *name;
> +     u32 event_code;
> +};
> +
> +static struct event_desc tune_events[] = {
> +     { "link_credit_rx_nph",         0x1 | (BIT(1) << 16) },
> +     { "link_credit_rx_npd",         0x1 | (BIT(2) << 16) },
> +     { "link_credit_rx_ph",          0x1 | (BIT(3) << 16) },
> +     { "link_credit_rx_pd",          0x1 | (BIT(4) << 16) },
> +     { "link_credit_rx_cplh",        0x1 | (BIT(5) << 16) },
> +     { "link_credit_rx_cpld",        0x1 | (BIT(6) << 16) },
> +     { "dllp_link_ack_freq",         0x2 | (BIT(1) << 16) },
> +     { "dllp_link_updatefc_freq",    0x2 | (BIT(2) << 16) },
> +     { "dllp_link_ssc",              0x2 | (BIT(2) << 16) },
> +     { "buf_tx_header",              0x3 | (BIT(1) << 16) },
> +     { "buf_tx_data",                0x3 | (BIT(2) << 16) },
> +     { "buf_rx_ph",                  0x3 | (BIT(3) << 16) },
> +     { "buf_rx_pd",                  0x3 | (BIT(4) << 16) },
> +     { "buf_rx_nph",                 0x3 | (BIT(5) << 16) },
> +     { "buf_rx_npd",                 0x3 | (BIT(6) << 16) },
> +     { "buf_rx_cplh",                0x3 | (BIT(7) << 16) },
> +     { "buf_rx_cpld",                0x3 | (BIT(8) << 16) },
> +     { "qos_tx_dp",                  0x4 | (BIT(1) << 16) },
> +     { "qos_rx_dp",                  0x4 | (BIT(1) << 16) },
> +};
> +
> +static struct event_desc trace_rxtx[] = {
> +     { "rx", 0 },
> +     { "tx", 1 },
> +     { "rxtx", 2 },
> +     { "rxtx_no_dma_p2p", 3 },
> +};
> +
> +static struct event_desc trace_events[] = {
> +     { "posted_request", 0 },
> +     { "non-posted_request",  2 },
> +     { "completion",  4 },
> +     { "all", 8 },
> +};
> +
> +static const int available_buflet_size[] = {
> +     0x00200000,     /* 2  MiB */
> +     0x00400000,     /* 4  MiB */
> +     0x00600000,     /* 6  MiB */
> +     0x00800000,     /* 8  MiB */
> +     0x00a00000,     /* 10 MiB */
> +};
> +
> +struct debugfs_file_desc {
> +     struct hisi_ptt *hisi_ptt;
> +     const char *name;
> +     const struct file_operations *fops;
> +     int index;
> +};
> +
> +struct dma_buflet {
> +     struct list_head list;
> +     dma_addr_t dma;
> +     void *addr;
> +     int index;
> +     u64 size;
> +};
> +
> +struct ptt_trace_ctrl {
> +     struct list_head trace_buf;
> +     struct dma_buflet *cur;
> +     atomic_t status; /* 0:idle, 1:tracing */
> +     u64 buflet_nums;
> +     u32 buflet_size;
> +     u32 tr_event;
> +     u32 rxtx;
> +};
> +
> +struct per_func_info {
> +     struct list_head list;
> +     struct pci_dev *pdev;
> +};
> +
> +struct hisi_ptt {
> +     struct ptt_trace_ctrl trace_ctrl;
> +     struct per_func_info *target_func;
> +     struct list_head avail_devfns;
> +     struct dentry *debugfs_dir;
> +     void __iomem *iobase;
> +     struct pci_dev *pdev;
> +     struct mutex mutex; /* protects the hisi_ptt structure */
> +     const char *name;
> +     u32 tune_event;
> +     u32 domain;
> +     u32 bus;
> +};
> +
> +static u32 hisi_ptt_tune_data_read(struct hisi_ptt *hisi_ptt)
> +{
> +     u32 val;
> +
> +     writel(hisi_ptt->tune_event, hisi_ptt->iobase + HISI_PTT_TUNING_CTRL);
> +
> +     val = readl(hisi_ptt->iobase + HISI_PTT_TUNING_DATA);
> +     val &= HISI_PTT_TUNING_DATA_VAL;
> +
> +     return val;
> +}
> +
> +static void hisi_ptt_tune_data_write(struct hisi_ptt *hisi_ptt, u32 data)
> +{
> +     writel(hisi_ptt->tune_event, hisi_ptt->iobase + HISI_PTT_TUNING_CTRL);
> +     data &= HISI_PTT_TUNING_DATA_VAL;
> +     writel(data, hisi_ptt->iobase + HISI_PTT_TUNING_DATA);
> +}
> +
> +static ssize_t hisi_ptt_tune_common_read(struct file *filp, char __user *buf,
> +                                      size_t count, loff_t *pos)
> +{
> +     struct debugfs_file_desc *desc = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN];
> +     int len;
> +     u32 val;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +     hisi_ptt->tune_event = tune_events[desc->index].event_code;
> +     val = hisi_ptt_tune_data_read(hisi_ptt);
> +     mutex_unlock(&hisi_ptt->mutex);
> +
> +     len = snprintf(tbuf, HISI_PTT_CTRL_STR_LEN,
> +                    "%d\n", val);
> +
> +     return simple_read_from_buffer(buf, count, pos, tbuf, len);
> +}
> +
> +static ssize_t hisi_ptt_tune_common_write(struct file *filp,
> +                     const char __user *buf, size_t count, loff_t *pos)
> +{
> +     struct debugfs_file_desc *desc = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN], *cp;
> +     int len, val;
> +
> +     len = simple_write_to_buffer(tbuf, HISI_PTT_CTRL_STR_LEN - 1,
> +                                  pos, buf, count);
> +     if (len < 0)
> +             return -EINVAL;
> +     cp = strchr(tbuf, '\n');
> +     if (cp)
> +             *cp = '\0';
> +     if (kstrtouint(tbuf, 0, &val))
> +             return -EINVAL;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +     hisi_ptt->tune_event = tune_events[desc->index].event_code;
> +     hisi_ptt_tune_data_write(hisi_ptt, val);
> +     mutex_unlock(&hisi_ptt->mutex);
> +
> +     return count;
> +}
> +
> +static const struct file_operations tune_common_fops = {
> +     .owner          = THIS_MODULE,
> +     .open           = simple_open,
> +     .read           = hisi_ptt_tune_common_read,
> +     .write          = hisi_ptt_tune_common_write,
> +     .llseek         = no_llseek,
> +};
> +
> +static void hisi_ptt_free_trace_buf(struct hisi_ptt *hisi_ptt)
> +{
> +     struct ptt_trace_ctrl *ctrl = &hisi_ptt->trace_ctrl;
> +     struct device *dev = &hisi_ptt->pdev->dev;
> +     struct dma_buflet *buflet, *tbuflet;
> +
> +     if (list_empty(&ctrl->trace_buf))
> +             return;
> +
> +     list_for_each_entry_safe(buflet, tbuflet, &ctrl->trace_buf, list) {
> +             dma_free_coherent(dev, buflet->size, buflet->addr, buflet->dma);
> +             list_del(&buflet->list);
> +             kfree(buflet);
> +     }
> +}
> +
> +static int hisi_ptt_alloc_trace_buf(struct hisi_ptt *hisi_ptt)
> +{
> +     struct ptt_trace_ctrl *ctrl = &hisi_ptt->trace_ctrl;
> +     struct device *dev = &hisi_ptt->pdev->dev;
> +     struct dma_buflet *buflet;
> +     int i, ret;
> +
> +     /* Make sure the trace buffer is empty before allocating */
> +     if (!list_empty(&ctrl->trace_buf))
> +             hisi_ptt_free_trace_buf(hisi_ptt);
> +
> +     for (i = 0; i < ctrl->buflet_nums; ++i) {
> +             buflet = kzalloc(sizeof(*buflet), GFP_KERNEL);
> +             if (!buflet) {
> +                     ret = -ENOMEM;
> +                     goto err;
> +             }
> +             buflet->addr = dma_alloc_coherent(dev, ctrl->buflet_size,
> +                                              &buflet->dma, GFP_KERNEL);
> +             if (!buflet->addr) {
> +                     kfree(buflet);
> +                     ret = -ENOMEM;
> +                     goto err;
> +             }
> +             buflet->index = i;
> +             buflet->size = ctrl->buflet_size;
> +             list_add_tail(&buflet->list, &ctrl->trace_buf);
> +     }
> +
> +     return 0;
> +err:
> +     hisi_ptt_free_trace_buf(hisi_ptt);
> +     return ret;
> +}
> +
> +static void hisi_ptt_trace_end(struct hisi_ptt *hisi_ptt)
> +{
> +     writel(0, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
> +     atomic_dec(&hisi_ptt->trace_ctrl.status);
> +     hisi_ptt->trace_ctrl.cur = NULL;
> +}
> +
> +static int hisi_ptt_trace_start(struct hisi_ptt *hisi_ptt)
> +{
> +     struct ptt_trace_ctrl *ctrl = &hisi_ptt->trace_ctrl;
> +     struct pci_dev *pdev, *rp;
> +     u32 val;
> +     int i;
> +
> +     if (!hisi_ptt->target_func) {
> +             pci_err(hisi_ptt->pdev, "No available root port/function\n");
> +             return -ENODEV;
> +     }
> +     pdev = hisi_ptt->target_func->pdev;
> +     rp = pcie_find_root_port(pdev);
> +
> +     val = readl(hisi_ptt->iobase + HISI_PTT_TRACE_STS);
> +     if (!(val & HISI_PTT_TRACE_IDLE)) /* Trace has already started */
> +             return -EBUSY;
> +
> +     /* reset the DMA before start tracing */
> +     val = readl(hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
> +     val |= HISI_PTT_TRACE_CTRL_RST;
> +     writel(val, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
> +
> +     msleep(HISI_PTT_RESET_WAIT_MS);
> +
> +     val = readl(hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
> +     val &= ~HISI_PTT_TRACE_CTRL_RST;
> +     writel(val, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
> +
> +     /* clear the interrupt status */
> +     writel(0, hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT);
> +
> +     for (i = 0; i < HISI_PTT_DMA_NUMS; ++i) {
> +             if (!ctrl->cur)
> +                     ctrl->cur = list_first_entry(&ctrl->trace_buf, struct 
> dma_buflet, list);
> +             else
> +                     ctrl->cur = list_next_entry(ctrl->cur, list);
> +
> +             writel(ctrl->cur->dma,
> +                    hisi_ptt->iobase + HISI_PTT_TRACE_ADDR_BASE_LO_0 + i * 
> 8);
> +             writel(ctrl->cur->dma >> 32,
> +                    hisi_ptt->iobase + HISI_PTT_TRACE_ADDR_BASE_HI_0 + i * 
> 8);
> +     }
> +     writel(ctrl->buflet_size, hisi_ptt->iobase + HISI_PTT_TRACE_ADDR_SIZE);
> +
> +     /* set the trace control register */
> +     val = 0;
> +     val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_TYPE_SEL, ctrl->tr_event);
> +     val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_RXTX_SEL, ctrl->rxtx);
> +     /*
> +      * The TLP headers can be filtered either by the root port,
> +      * or by the requester ID.
> +      */
> +     if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT) {
> +             val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_TARGET_SEL, 
> pdev->bus->number << 8 | pdev->devfn);
> +             val |= HISI_PTT_TRACE_CTRL_FILTER_MODE;
> +     } else {
> +             val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_TARGET_SEL, 
> BIT(PCI_SLOT(rp->devfn)));
> +     }
> +
> +     val |= HISI_PTT_TRACE_CTRL_EN;
> +     writel(val, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
> +
> +     atomic_inc(&ctrl->status);
> +
> +     return 0;
> +}
> +
> +#define TRACE_ATTR(__name)                                           \
> +static int __name ## _open(struct inode *inode, struct file *filp)   \
> +{                                                                    \
> +     struct debugfs_file_desc *desc = inode->i_private;              \
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;                     \
> +     if (!mutex_trylock(&hisi_ptt->mutex))                           \
> +             return -EBUSY;                                          \
> +     return single_open(filp, __name ## _show, hisi_ptt);            \
> +}                                                                    \
> +static int __name ## _release(struct inode *inode, struct file *filp)        
> \
> +{                                                                    \
> +     struct debugfs_file_desc *desc = inode->i_private;              \
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;                     \
> +     mutex_unlock(&hisi_ptt->mutex);                                 \
> +     return seq_release(inode, filp);                                \
> +}                                                                    \
> +static const struct file_operations __name ## _fops = {                      
> \
> +     .owner          = THIS_MODULE,                                  \
> +     .open           = __name ## _open,                              \
> +     .read           = seq_read,                                     \
> +     .write          = __name ## _write,                             \
> +     .llseek         = seq_lseek,                                    \
> +     .release        = __name ## _release,                           \
> +}
> +
> +static int set_filter_show(struct seq_file *m, void *v)
> +{
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     struct per_func_info *func;
> +
> +     list_for_each_entry(func, &hisi_ptt->avail_devfns, list) {
> +             struct pci_dev *pdev = func->pdev;
> +
> +             if (func == hisi_ptt->target_func)
> +                     seq_printf(m, "[%04x:%02x:%02x.%d]\n",
> +                                pci_domain_nr(pdev->bus),
> +                                pdev->bus->number, PCI_SLOT(pdev->devfn),
> +                                PCI_FUNC(pdev->devfn));
> +             else
> +                     seq_printf(m, "%04x:%02x:%02x.%d\n",
> +                                pci_domain_nr(pdev->bus),
> +                                pdev->bus->number, PCI_SLOT(pdev->devfn),
> +                                PCI_FUNC(pdev->devfn));
> +     }
> +
> +     return 0;
> +}
> +
> +static ssize_t set_filter_write(struct file *filp, const char __user *buf,
> +                                 size_t count, loff_t *pos)
> +{
> +     struct seq_file *m = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN], *cp;
> +     u32 domain, bus, dev, fn, devfn;
> +     struct per_func_info *tfunc;
> +     int len, num;
> +     bool found = false;
> +
> +     if (list_empty(&hisi_ptt->avail_devfns))
> +             return -EINVAL;
> +     len = simple_write_to_buffer(tbuf, HISI_PTT_CTRL_STR_LEN - 1, pos, buf, 
> count);
> +     if (len < 0)
> +             return -EINVAL;
> +     cp = strchr(tbuf, '\n');
> +     if (cp)
> +             *cp = '\0';
> +
> +     /*
> +      * the input should be like 0000:80:01.1, etc. Parse it
> +      * and check whether it's in the available func list.
> +      */
> +     num = sscanf(tbuf, "%04x:%02x:%02x.%d", &domain, &bus, &dev, &fn);
> +     if (num != 4)
> +             return -EINVAL;
> +
> +     devfn = PCI_DEVFN(dev, fn);
> +     list_for_each_entry(tfunc, &hisi_ptt->avail_devfns, list) {
> +             struct pci_dev *pdev = tfunc->pdev;
> +
> +             if (domain == pci_domain_nr(pdev->bus) &&
> +                 bus == pdev->bus->number && devfn == pdev->devfn) {
> +                     hisi_ptt->target_func = tfunc;
> +                     found = true;
> +                     break;
> +             }
> +     }
> +
> +     if (!found)
> +             return -EINVAL;
> +
> +     return count;
> +}
> +TRACE_ATTR(set_filter);
> +
> +static int set_direction_show(struct seq_file *m, void *v)
> +{
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     int i;
> +
> +     for (i = 0; i < ARRAY_SIZE(trace_rxtx); ++i) {
> +             if (hisi_ptt->trace_ctrl.rxtx == trace_rxtx[i].event_code)
> +                     seq_printf(m, "[%s]  ", trace_rxtx[i].name);
> +             else
> +                     seq_printf(m, "%s  ", trace_rxtx[i].name);
> +     }
> +     seq_putc(m, '\n');
> +
> +     return 0;
> +}
> +
> +static ssize_t set_direction_write(struct file *filp, const char __user *buf,
> +                                size_t count, loff_t *pos)
> +{
> +     struct seq_file *m = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN], *cp;
> +     bool set = false;
> +     int len, i;
> +
> +     len = simple_write_to_buffer(tbuf, HISI_PTT_CTRL_STR_LEN, pos, buf, 
> count);
> +     if (len < 0)
> +             return -EINVAL;
> +     cp = strchr(tbuf, '\n');
> +     if (cp)
> +             *cp = '\0';
> +
> +     for (i = 0; i < ARRAY_SIZE(trace_rxtx); ++i) {
> +             if (!strcmp(tbuf, trace_rxtx[i].name)) {
> +                     hisi_ptt->trace_ctrl.rxtx = trace_rxtx[i].event_code;
> +                     set = true;
> +                     break;
> +             }
> +     }
> +
> +     if (!set)
> +             return -EINVAL;
> +
> +     return 0;
> +}
> +TRACE_ATTR(set_direction);
> +
> +static int set_trace_type_show(struct seq_file *m, void *v)
> +{
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     int i;
> +
> +     for (i = 0; i < ARRAY_SIZE(trace_events); ++i) {
> +             if (hisi_ptt->trace_ctrl.tr_event == trace_events[i].event_code)
> +                     seq_printf(m, "[%s]  ", trace_events[i].name);
> +             else
> +                     seq_printf(m, "%s  ", trace_events[i].name);
> +     }
> +     seq_putc(m, '\n');
> +
> +     return 0;
> +}
> +
> +static ssize_t set_trace_type_write(struct file *filp, const char __user 
> *buf,
> +                            size_t count, loff_t *pos)
> +{
> +     struct seq_file *m = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN], *cp;
> +     bool set = false;
> +     int len, i;
> +
> +     len = simple_write_to_buffer(tbuf, HISI_PTT_CTRL_STR_LEN, pos, buf, 
> count);
> +     if (len < 0)
> +             return -EINVAL;
> +     cp = strchr(tbuf, '\n');
> +     if (cp)
> +             *cp = '\0';
> +
> +     for (i = 0; i < ARRAY_SIZE(trace_events); ++i) {
> +             if (!strcmp(tbuf, trace_events[i].name)) {
> +                     hisi_ptt->trace_ctrl.tr_event = 
> trace_events[i].event_code;
> +                     set = true;
> +                     break;
> +             }
> +     }
> +
> +     if (!set)
> +             return -EINVAL;
> +
> +     return count;
> +}
> +TRACE_ATTR(set_trace_type);
> +
> +static int trace_on_get(void *data, u64 *val)
> +{
> +     struct debugfs_file_desc *desc = data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     *val = atomic_read(&hisi_ptt->trace_ctrl.status);
> +
> +     mutex_unlock(&hisi_ptt->mutex);
> +
> +     return 0;
> +}
> +
> +static int trace_on_set(void *data, u64 val)
> +{
> +     struct debugfs_file_desc *desc = data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +     int ret = 0;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     if (val) {
> +             if (atomic_read(&hisi_ptt->trace_ctrl.status))
> +                     goto out;
> +             if (hisi_ptt_alloc_trace_buf(hisi_ptt)) {
> +                     ret = -ENOMEM;
> +                     goto out;
> +             }
> +             if (hisi_ptt_trace_start(hisi_ptt)) {
> +                     ret = -EBUSY;
> +                     goto out;
> +             }
> +     } else {
> +             if (!atomic_read(&hisi_ptt->trace_ctrl.status))
> +                     goto out;
> +             hisi_ptt_trace_end(hisi_ptt);
> +     }
> +
> +out:
> +     hisi_ptt_free_trace_buf(hisi_ptt);
> +     mutex_unlock(&hisi_ptt->mutex);
> +     return ret;
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(trace_on_fops, trace_on_get,
> +                      trace_on_set, "%lld\n");
> +
> +static int set_trace_buf_nums_get(void *data, u64 *val)
> +{
> +     struct debugfs_file_desc *desc = data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     *val = hisi_ptt->trace_ctrl.buflet_nums;
> +
> +     mutex_unlock(&hisi_ptt->mutex);
> +     return 0;
> +}
> +
> +static int set_trace_buf_nums_set(void *data, u64 val)
> +{
> +     struct debugfs_file_desc *desc = data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     hisi_ptt->trace_ctrl.buflet_nums = val;
> +
> +     mutex_unlock(&hisi_ptt->mutex);
> +     return 0;
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(set_trace_buf_nums_fops, set_trace_buf_nums_get,
> +                      set_trace_buf_nums_set, "%lld\n");
> +
> +static int set_trace_buflet_size_show(struct seq_file *m, void *v)
> +{
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     struct ptt_trace_ctrl *ctrl = &hisi_ptt->trace_ctrl;
> +     int i;
> +
> +     for (i = 0; i < ARRAY_SIZE(available_buflet_size); ++i) {
> +             if (ctrl->buflet_size == available_buflet_size[i]) {
> +                     seq_printf(m, "[%dMiB]  ",
> +                                available_buflet_size[i] >> 20);
> +                     continue;
> +             }
> +             seq_printf(m, "%dMiB  ", available_buflet_size[i] >> 20);
> +     }
> +     seq_putc(m, '\n');
> +
> +     return 0;
> +}
> +
> +static ssize_t set_trace_buflet_size_write(struct file *filp,
> +                     const char __user *buf, size_t count, loff_t *pos)
> +{
> +     struct seq_file *m = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     struct ptt_trace_ctrl *ctrl = &hisi_ptt->trace_ctrl;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN], *cp;
> +     int i, len, size, set = 0;
> +
> +     len = simple_write_to_buffer(tbuf, HISI_PTT_CTRL_STR_LEN, pos, buf, 
> count);
> +     cp = strchr(tbuf, '\n');
> +     if (cp)
> +             *cp = '\0';
> +     if (kstrtouint(tbuf, 0, &size))
> +             return -EINVAL;
> +     size <<= 20;
> +
> +     for (i = 0; i < ARRAY_SIZE(available_buflet_size); ++i) {
> +             if (available_buflet_size[i] == size) {
> +                     ctrl->buflet_size = size;
> +                     set = 1;
> +                     break;
> +             }
> +     }
> +
> +     if (!set)
> +             return -EINVAL;
> +
> +     return count;
> +}
> +TRACE_ATTR(set_trace_buflet_size);
> +
> +static int free_trace_buf_get(void *data, u64 *val)
> +{
> +     struct debugfs_file_desc *desc = data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     *val = list_empty(&hisi_ptt->trace_ctrl.trace_buf);
> +
> +     mutex_unlock(&hisi_ptt->mutex);
> +     return 0;
> +}
> +
> +static int free_trace_buf_set(void *data, u64 val)
> +{
> +     struct debugfs_file_desc *desc = data;
> +     struct hisi_ptt *hisi_ptt = desc->hisi_ptt;
> +     int ret = 0;
> +
> +     if (!mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     if (!list_empty(&hisi_ptt->trace_ctrl.trace_buf)) {
> +             if (atomic_read(&hisi_ptt->trace_ctrl.status))
> +                     ret = -EBUSY;
> +             else
> +                     hisi_ptt_free_trace_buf(hisi_ptt);
> +     }
> +
> +     mutex_unlock(&hisi_ptt->mutex);
> +     return ret;
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(free_trace_buf_fops, free_trace_buf_get,
> +                      free_trace_buf_set, "%lld\n");
> +
> +static void *trace_data_next(struct seq_file *m, void *v, loff_t *pos)
> +{
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     struct dma_buflet *buflet = v;
> +
> +     (*pos)++;
> +
> +     if (!list_is_last(&buflet->list, &hisi_ptt->trace_ctrl.trace_buf))
> +             return list_next_entry(buflet, list);
> +
> +     return NULL;
> +}
> +
> +static void *trace_data_start(struct seq_file *m, loff_t *pos)
> +{
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     struct dma_buflet *buflet;
> +     loff_t off = 0;
> +
> +     buflet = list_first_entry(&hisi_ptt->trace_ctrl.trace_buf, struct 
> dma_buflet, list);
> +     while (off < *pos)
> +             buflet = trace_data_next(m, buflet, &off);
> +
> +     return buflet;
> +}
> +
> +static void trace_data_stop(struct seq_file *m, void *p)
> +{
> +     /* Nothing to do, only a stub */
> +}
> +
> +static int trace_data_show(struct seq_file *m, void *v)
> +{
> +     struct dma_buflet *buflet = v;
> +
> +     if (buflet)
> +             seq_write(m, buflet->addr, buflet->size);
> +
> +     return 0;
> +}
> +
> +static const struct seq_operations trace_data_seq_ops = {
> +     .start  = trace_data_start,
> +     .next   = trace_data_next,
> +     .stop   = trace_data_stop,
> +     .show   = trace_data_show,
> +};
> +
> +static int trace_data_open(struct inode *inode, struct file *filep)
> +{
> +     struct hisi_ptt *hisi_ptt = inode->i_private;
> +     struct seq_file *m;
> +     int ret;
> +
> +     /*
> +      * Check the trace status, we cannot read the
> +      * data if the trace is still on. Then hold the
> +      * lock when reading the traced data.
> +      */
> +     if (atomic_read(&hisi_ptt->trace_ctrl.status) ||
> +         !mutex_trylock(&hisi_ptt->mutex))
> +             return -EBUSY;
> +
> +     if (list_empty(&hisi_ptt->trace_ctrl.trace_buf)) {
> +             ret = -ENOTTY;
> +             goto err;
> +     }
> +
> +     ret = seq_open(filep, &trace_data_seq_ops);
> +     if (ret)
> +             goto err;
> +
> +     m = filep->private_data;
> +     m->private = hisi_ptt;
> +
> +     return 0;
> +err:
> +     mutex_unlock(&hisi_ptt->mutex);
> +     return ret;
> +}
> +
> +static ssize_t trace_data_write(struct file *filp, const char __user *buf,
> +                             size_t count, loff_t *off)
> +{
> +     struct seq_file *m = filp->private_data;
> +     struct hisi_ptt *hisi_ptt = m->private;
> +     char tbuf[HISI_PTT_CTRL_STR_LEN], *cp;
> +     int len;
> +
> +     len = simple_write_to_buffer(tbuf, HISI_PTT_CTRL_STR_LEN, off, buf, 
> count);
> +     cp = strchr(tbuf, '\n');
> +     if (cp)
> +             *cp = '\0';
> +
> +     /* Free the trace buffer when echo "" > trace_data */
> +     if (!strlen(tbuf) && !list_empty(&hisi_ptt->trace_ctrl.trace_buf)) {
> +             if (atomic_read(&hisi_ptt->trace_ctrl.status))
> +                     return -EBUSY;
> +             hisi_ptt_free_trace_buf(hisi_ptt);
> +     }
> +
> +     return count;
> +}
> +
> +static int trace_data_release(struct inode *inode, struct file *filp)
> +{
> +     struct hisi_ptt *hisi_ptt = inode->i_private;
> +
> +     mutex_unlock(&hisi_ptt->mutex);
> +
> +     return seq_release(inode, filp);
> +}
> +
> +static const struct file_operations trace_data_fops = {
> +     .owner          = THIS_MODULE,
> +     .open           = trace_data_open,
> +     .read           = seq_read,
> +     .write          = trace_data_write,
> +     .llseek         = no_llseek,
> +     .release        = trace_data_release,
> +};
> +
> +static struct debugfs_file_desc trace_entries[] = {
> +     { NULL, "filter", &set_filter_fops, 0 },
> +     { NULL, "direction", &set_direction_fops, 0 },
> +     { NULL, "type", &set_trace_type_fops, 0 },
> +     { NULL, "trace_on", &trace_on_fops, 0 },
> +     { NULL, "buf_nums", &set_trace_buf_nums_fops, 0 },
> +     { NULL, "buflet_size", &set_trace_buflet_size_fops, 0 },
> +     { NULL, "free_buffer", &free_trace_buf_fops, 0 },
> +     { NULL, "data", &trace_data_fops, 0 },
> +};
> +
> +irqreturn_t hisi_ptt_isr(int irq, void *context)
> +{
> +     struct hisi_ptt *hisi_ptt = context;
> +     struct dma_buflet *next, *cur;
> +     u32 val, buf_idx;
> +
> +     val = readl(hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT);
> +     buf_idx = __ffs(val & HISI_PTT_TRACE_INT_STAT_MASK);
> +     /*
> +      * Check whether the trace buffer is full. Stop tracing
> +      * when the last DMA buffer is finished. Otherwise, assign
> +      * the address of next buflet to the DMA register.
> +      */
> +     cur = hisi_ptt->trace_ctrl.cur;
> +     if (list_is_last(&cur->list, &hisi_ptt->trace_ctrl.trace_buf)) {
> +             if ((val & HISI_PTT_TRACE_INT_STAT_MASK) == 
> HISI_PTT_TRACE_INT_STAT_MASK)
> +                     hisi_ptt_trace_end(hisi_ptt);
> +     } else {
> +             next = list_next_entry(cur, list);
> +             writel(next->dma, hisi_ptt->iobase + 
> HISI_PTT_TRACE_ADDR_BASE_LO_0 + buf_idx * 8);
> +             writel(next->dma >> 32, hisi_ptt->iobase + 
> HISI_PTT_TRACE_ADDR_BASE_HI_0 + buf_idx * 8);
> +             hisi_ptt->trace_ctrl.cur = next;
> +             val &= ~BIT(buf_idx);
> +             writel(val, hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT);
> +     }
> +
> +     return IRQ_HANDLED;
> +}
> +
> +irqreturn_t hisi_ptt_irq(int irq, void *context)
> +{
> +     struct hisi_ptt *hisi_ptt = context;
> +     u32 status;
> +
> +     status = readl(hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT);
> +     if (!(status & HISI_PTT_TRACE_INT_STAT_MASK))
> +             return IRQ_NONE;
> +
> +     return IRQ_WAKE_THREAD;
> +}
> +
> +static int hisi_ptt_irq_register(struct hisi_ptt *hisi_ptt)
> +{
> +     struct pci_dev *pdev = hisi_ptt->pdev;
> +     int ret;
> +
> +     ret = pci_alloc_irq_vectors(pdev, HISI_PTT_IRQ_NUMS, HISI_PTT_IRQ_NUMS,
> +                                 PCI_IRQ_MSI);
> +     if (ret < 0) {
> +             pci_err(pdev, "allocate irq vector failed %d", ret);
> +             return ret;
> +     }
> +
> +     ret = request_threaded_irq(pci_irq_vector(pdev, HISI_PTT_DMA_IRQ),
> +                                hisi_ptt_irq, hisi_ptt_isr, IRQF_SHARED,
> +                                hisi_ptt->name, hisi_ptt);
> +
> +     if (ret) {
> +             pci_err(pdev, "request irq %d failed",
> +                     pci_irq_vector(pdev, HISI_PTT_DMA_IRQ));
> +             pci_free_irq_vectors(pdev);
> +             return ret;
> +     }
> +
> +     return 0;
> +}
> +
> +static void hisi_ptt_irq_unregister(struct hisi_ptt *hisi_ptt)
> +{
> +     struct pci_dev *pdev = hisi_ptt->pdev;
> +
> +     free_irq(pci_irq_vector(pdev, HISI_PTT_DMA_IRQ), hisi_ptt);
> +     pci_free_irq_vectors(pdev);
> +}
> +
> +static void hisi_ptt_init_ctrls(struct hisi_ptt *hisi_ptt)
> +{
> +     struct pci_dev *pdev = hisi_ptt->pdev, *tpdev;
> +     struct pci_bus *child_bus;
> +     unsigned long port_mask, bit;
> +
> +     hisi_ptt->domain = pci_domain_nr(pdev->bus);
> +     hisi_ptt->bus = pdev->bus->number;
> +     /*
> +      * The mailbox register provides the information about the
> +      * root ports which the RCiEP can control and monitor.
> +      */
> +     port_mask = readl(hisi_ptt->iobase + HISI_PTT_MAILBOX_0);
> +
> +     /*
> +      * The ports traced by the RCiEP are masked by the register.
> +      * Some ports may not exists even if they are masked. We'll check
> +      * whether one port is enabled by finding its pci_dev structure
> +      * in the device list, and add it and its subordinates
> +      * in the available devfns list.
> +      */
> +     for_each_set_bit(bit, &port_mask, sizeof(port_mask) * 8) {
> +             struct per_func_info *func;
> +
> +             tpdev = pci_get_domain_bus_and_slot(hisi_ptt->domain,
> +                                                 hisi_ptt->bus,
> +                                                 PCI_DEVFN(bit, 0));
> +             /*
> +              * If the root port is not existed in the system,
> +              * just skip it and check next one.
> +              */
> +             if (!tpdev)
> +                     continue;
> +
> +             func = devm_kmalloc(&pdev->dev, sizeof(*func), GFP_KERNEL);
> +             if (!func)
> +                     continue;
> +             func->pdev = tpdev;
> +             pci_dev_put(tpdev);
> +
> +             list_add_tail(&func->list, &hisi_ptt->avail_devfns);
> +
> +             /*
> +              * The PTT can designate function for trace.
> +              * Add the root port's subordinates in the list as we
> +              * can specify certain function.
> +              */
> +             child_bus = tpdev->subordinate;
> +             list_for_each_entry(tpdev, &child_bus->devices, bus_list) {
> +                     func = devm_kmalloc(&pdev->dev, sizeof(*func),
> +                                         GFP_KERNEL);
> +                     if (!func)
> +                             continue;
> +                     func->pdev = tpdev;
> +                     list_add_tail(&func->list, &hisi_ptt->avail_devfns);
> +             }
> +     }
> +
> +     /* Initialize the target function */
> +     if (!list_empty(&hisi_ptt->avail_devfns))
> +             hisi_ptt->target_func = 
> list_first_entry(&hisi_ptt->avail_devfns,
> +                                             struct per_func_info, list);
> +
> +     /* Initialize trace controls */
> +     INIT_LIST_HEAD(&hisi_ptt->trace_ctrl.trace_buf);
> +     hisi_ptt->trace_ctrl.buflet_nums = HISI_PTT_DEFAULT_TRACE_BUF_CNT;
> +     hisi_ptt->trace_ctrl.buflet_size = available_buflet_size[0];
> +     hisi_ptt->trace_ctrl.tr_event = trace_events[0].event_code;
> +     hisi_ptt->trace_ctrl.rxtx = trace_rxtx[0].event_code;
> +}
> +
> +static int hisi_ptt_create_debugfs_entries(struct hisi_ptt *hisi_ptt)
> +{
> +     struct dentry *tdir;
> +     int i;
> +
> +     hisi_ptt->debugfs_dir = debugfs_create_dir(hisi_ptt->name,
> +                                                hisi_ptt_debugfs_root);
> +     if (IS_ERR(hisi_ptt->debugfs_dir))
> +             return -ENOENT;
> +
> +     tdir = debugfs_create_dir("tune", hisi_ptt->debugfs_dir);
> +     if (IS_ERR(tdir))
> +             goto err;
> +     for (i = 0; i < ARRAY_SIZE(tune_events); ++i) {
> +             struct debugfs_file_desc *tune_file;
> +
> +             tune_file = devm_kzalloc(&hisi_ptt->pdev->dev,
> +                                      sizeof(*tune_file), GFP_KERNEL);
> +             if (!tune_file)
> +                     goto err;
> +             tune_file->hisi_ptt = hisi_ptt;
> +             /* We use tune event string as control file name. */
> +             tune_file->name = tune_events[i].name;
> +             tune_file->fops = &tune_common_fops;
> +             tune_file->index = i;
> +             if (IS_ERR(debugfs_create_file(tune_events[i].name, 0600,
> +                                            tdir, tune_file,
> +                                            &tune_common_fops)))
> +                     goto err;
> +     }
> +
> +     tdir = debugfs_create_dir("trace", hisi_ptt->debugfs_dir);
> +     if (IS_ERR(tdir))
> +             goto err;
> +     for (i = 0; i < ARRAY_SIZE(trace_entries); ++i) {
> +             trace_entries[i].hisi_ptt = hisi_ptt;
> +             trace_entries[i].index = i;
> +             if (IS_ERR(debugfs_create_file(trace_entries[i].name, 0600,
> +                                            tdir, &trace_entries[i],
> +                                            trace_entries[i].fops)))
> +                     goto err;
> +     }
> +
> +     return 0;
> +err:
> +     pci_err(hisi_ptt->pdev, "create debugfs entries failed\n");
> +     debugfs_remove_recursive(hisi_ptt->debugfs_dir);
> +     return -ENOENT;
> +}
> +
> +static int hisi_ptt_probe(struct pci_dev *pdev,
> +                       const struct pci_device_id *id)
> +{
> +     struct hisi_ptt *hisi_ptt;
> +     int ret;
> +
> +     hisi_ptt = devm_kzalloc(&pdev->dev, sizeof(*hisi_ptt), GFP_KERNEL);
> +     if (!hisi_ptt)
> +             return -ENOMEM;
> +
> +     mutex_init(&hisi_ptt->mutex);
> +     INIT_LIST_HEAD(&hisi_ptt->avail_devfns);
> +     hisi_ptt->pdev = pdev;
> +     /*
> +      * Lifetime of pci_dev is longer than hisi_ptt,
> +      * so directly reference to the pci name string.
> +      */
> +     hisi_ptt->name = pci_name(pdev);
> +     pci_set_drvdata(pdev, hisi_ptt);
> +
> +     ret = pcim_enable_device(pdev);
> +     if (ret) {
> +             pci_err(pdev, "fail to enable device\n");
> +             return ret;
> +     }
> +
> +     ret = pcim_iomap_regions(pdev, BIT(0), hisi_ptt->name);
> +     if (ret) {
> +             pci_err(pdev, "fail to remap io memory\n");
> +             return ret;
> +     }
> +
> +     ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
> +     if (ret) {
> +             pci_err(pdev, "fail to set 64 bit dma mask %d", ret);
> +             return ret;
> +     }
> +     pci_set_master(pdev);
> +
> +     ret = hisi_ptt_irq_register(hisi_ptt);
> +     if (ret)
> +             return ret;
> +
> +     hisi_ptt_init_ctrls(hisi_ptt);
> +
> +     ret = hisi_ptt_create_debugfs_entries(hisi_ptt);
> +     if (ret) {
> +             hisi_ptt_irq_unregister(hisi_ptt);
> +             return ret;
> +     }
> +
> +     return 0;
> +}
> +
> +void hisi_ptt_remove(struct pci_dev *pdev)
> +{
> +     struct hisi_ptt *hisi_ptt = pci_get_drvdata(pdev);
> +
> +     hisi_ptt_free_trace_buf(hisi_ptt);
> +     debugfs_remove_recursive(hisi_ptt->debugfs_dir);
> +     hisi_ptt_irq_unregister(hisi_ptt);
> +}
> +
> +static pci_ers_result_t hisi_ptt_err_detected(struct pci_dev *pdev,
> +                                             pci_channel_state_t state)
> +{
> +     /* The PTT device doesn't support error recovery */
> +     return PCI_ERS_RESULT_RECOVERED;
> +}
> +
> +static const struct pci_error_handlers hisi_ptt_err_handler = {
> +     .error_detected = hisi_ptt_err_detected,
> +};
> +
> +#define PCI_DEVICE_ID_HISI_PTT 0xa250
> +static const struct pci_device_id hisi_ptt_id_tbl[] = {
> +     { PCI_DEVICE(PCI_VENDOR_ID_HUAWEI, PCI_DEVICE_ID_HISI_PTT) },
> +     { 0, }
> +};
> +
> +static struct pci_driver hisi_ptt_driver = {
> +     .name = "hisi_ptt",
> +     .id_table = hisi_ptt_id_tbl,
> +     .probe = hisi_ptt_probe,
> +     .remove = hisi_ptt_remove,
> +     .err_handler = &hisi_ptt_err_handler,
> +};
> +
> +static int hisi_ptt_register_debugfs(void)
> +{
> +     if (!debugfs_initialized()) {
> +             pr_err("error: debugfs uninitialized\n");
> +             return -ENOENT;
> +     }
> +
> +     hisi_ptt_debugfs_root = debugfs_create_dir("hisi_ptt", NULL);
> +     if (IS_ERR(hisi_ptt_debugfs_root)) {
> +             pr_err("error: fail to create debugfs directory\n");
> +             return -ENOENT;
> +     }
> +
> +     return 0;
> +}
> +
> +static void hisi_ptt_unregister_debugfs(void)
> +{
> +     debugfs_remove_recursive(hisi_ptt_debugfs_root);
> +}
> +
> +static int __init hisi_ptt_module_init(void)
> +{
> +     int ret;
> +
> +     /* The driver cannot work without debugfs entry */
> +     ret = hisi_ptt_register_debugfs();
> +     if (ret)
> +             return ret;
> +
> +     ret = pci_register_driver(&hisi_ptt_driver);
> +     if (ret) {
> +             pr_err("error: fail to register hisi ptt driver\n");
> +             hisi_ptt_unregister_debugfs();
> +             return ret;
> +     }
> +
> +     return 0;
> +}
> +
> +static void __exit hisi_ptt_module_exit(void)
> +{
> +     pci_unregister_driver(&hisi_ptt_driver);
> +     hisi_ptt_unregister_debugfs();
> +}
> +
> +module_init(hisi_ptt_module_init);
> +module_exit(hisi_ptt_module_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Yicong Yang <[email protected]>");
> +MODULE_DESCRIPTION("Driver for HiSilicon PCIe tune and trace device");

Reply via email to