[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <59f149a3-c297-797f-5eff-0858e3185a87@hisilicon.com>
Date: Wed, 24 Jun 2020 18:20:45 +0800
From: Yicong Yang <yangyicong@...ilicon.com>
To: <linux-kernel@...r.kernel.org>,
<alexander.shishkin@...ux.intel.com>, <helgaas@...nel.org>,
<linux-pci@...r.kernel.org>
CC: <linuxarm@...wei.com>, <mathieu.poirier@...aro.org>,
<suzuki.poulose@....com>, <mike.leach@...aro.org>
Subject: Re: [RFC PATCH] hwtracing: Add HiSilicon PCIe Tune and Trace device
driver
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 <yangyicong@...ilicon.com>
> ---
> 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 <yangyicong@...ilicon.com>");
> +MODULE_DESCRIPTION("Driver for HiSilicon PCIe tune and trace device");
Powered by blists - more mailing lists