@@ -270,27 +270,121 @@ static int hisi_ptt_register_irq(struct hisi_ptt *hisi_ptt)
return 0;
}
-static int hisi_ptt_init_filters(struct pci_dev *pdev, void *data)
+static void hisi_ptt_update_filters(struct work_struct *work)
{
+ struct delayed_work *delayed_work = to_delayed_work(work);
+ struct hisi_ptt_filter_update_info info;
struct hisi_ptt_filter_desc *filter;
- struct hisi_ptt *hisi_ptt = data;
struct list_head *target_list;
+ struct hisi_ptt *hisi_ptt;
- target_list = pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT ?
- &hisi_ptt->port_filters : &hisi_ptt->req_filters;
+ hisi_ptt = container_of(delayed_work, struct hisi_ptt, work);
- filter = kzalloc(sizeof(*filter), GFP_KERNEL);
- if (!filter) {
- pci_err(hisi_ptt->pdev, "failed to add filter %s\n", pci_name(pdev));
- return -ENOMEM;
+ if (!mutex_trylock(&hisi_ptt->mutex)) {
+ schedule_delayed_work(&hisi_ptt->work, HISI_PTT_WORK_DELAY_MS);
+ return;
}
- filter->pdev = pdev;
- list_add_tail(&filter->list, target_list);
+ while (kfifo_get(&hisi_ptt->filter_update_kfifo, &info)) {
+ bool is_port = pci_pcie_type(info.pdev) == PCI_EXP_TYPE_ROOT_PORT;
+ u16 val = hisi_ptt_get_filter_val(info.pdev);
- /* Update the available port mask */
- if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT)
- hisi_ptt->port_mask |= hisi_ptt_get_filter_val(pdev);
+ target_list = is_port ? &hisi_ptt->port_filters : &hisi_ptt->req_filters;
+
+ if (info.is_add) {
+ filter = kzalloc(sizeof(*filter), GFP_KERNEL);
+ if (!filter) {
+ pci_err(hisi_ptt->pdev, "failed to add filter %s\n",
+ pci_name(info.pdev));
+ continue;
+ }
+
+ filter->pdev = info.pdev;
+ list_add_tail(&filter->list, target_list);
+ } else {
+ list_for_each_entry(filter, target_list, list)
+ if (hisi_ptt_get_filter_val(filter->pdev) == val) {
+ list_del(&filter->list);
+ kfree(filter);
+ break;
+ }
+ }
+
+ /* Update the available port mask */
+ if (!is_port)
+ continue;
+
+ if (info.is_add)
+ hisi_ptt->port_mask |= val;
+ else
+ hisi_ptt->port_mask &= ~val;
+ }
+
+ mutex_unlock(&hisi_ptt->mutex);
+}
+
+static void hisi_ptt_update_fifo_in(struct hisi_ptt *hisi_ptt,
+ struct hisi_ptt_filter_update_info *info)
+{
+ struct pci_dev *root_port = pcie_find_root_port(info->pdev);
+ u32 port_devid;
+
+ if (!root_port)
+ return;
+
+ port_devid = PCI_DEVID(root_port->bus->number, root_port->devfn);
+ if (port_devid < hisi_ptt->lower ||
+ port_devid > hisi_ptt->upper)
+ return;
+
+ if (kfifo_in_spinlocked(&hisi_ptt->filter_update_kfifo, info, 1,
+ &hisi_ptt->filter_update_lock))
+ schedule_delayed_work(&hisi_ptt->work, 0);
+ else
+ pci_warn(hisi_ptt->pdev,
+ "filter update fifo overflow for target %s\n",
+ pci_name(info->pdev));
+}
+
+/*
+ * A PCI bus notifier is used here for dynamically updating the filter
+ * list.
+ */
+static int hisi_ptt_notifier_call(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct hisi_ptt *hisi_ptt = container_of(nb, struct hisi_ptt, hisi_ptt_nb);
+ struct hisi_ptt_filter_update_info info;
+ struct device *dev = data;
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ info.pdev = pdev;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ info.is_add = true;
+ break;
+ case BUS_NOTIFY_DEL_DEVICE:
+ info.is_add = false;
+ break;
+ default:
+ return 0;
+ }
+
+ hisi_ptt_update_fifo_in(hisi_ptt, &info);
+
+ return 0;
+}
+
+static int hisi_ptt_init_filters(struct pci_dev *pdev, void *data)
+{
+ struct hisi_ptt_filter_update_info info = {
+ .pdev = pdev,
+ .is_add = true,
+ };
+ struct hisi_ptt *hisi_ptt = data;
+
+ hisi_ptt_update_fifo_in(hisi_ptt, &info);
return 0;
}
@@ -318,6 +412,9 @@ static int hisi_ptt_init_ctrls(struct hisi_ptt *hisi_ptt)
int ret;
u32 reg;
+ INIT_DELAYED_WORK(&hisi_ptt->work, hisi_ptt_update_filters);
+ spin_lock_init(&hisi_ptt->filter_update_lock);
+ INIT_KFIFO(hisi_ptt->filter_update_kfifo);
INIT_LIST_HEAD(&hisi_ptt->port_filters);
INIT_LIST_HEAD(&hisi_ptt->req_filters);
@@ -334,6 +431,13 @@ static int hisi_ptt_init_ctrls(struct hisi_ptt *hisi_ptt)
hisi_ptt->upper = FIELD_GET(HISI_PTT_DEVICE_RANGE_UPPER, reg);
hisi_ptt->lower = FIELD_GET(HISI_PTT_DEVICE_RANGE_LOWER, reg);
+ /*
+ * No need to fail if the bus is NULL here as the device
+ * maybe hotplugged after the PTT driver probe, in which
+ * case we can detect the event and update the list as
+ * we register a bus notifier for dynamically updating
+ * the filter list.
+ */
bus = pci_find_bus(pci_domain_nr(pdev->bus), PCI_BUS_NUM(hisi_ptt->upper));
if (bus)
pci_walk_bus(bus, hisi_ptt_init_filters, hisi_ptt);
@@ -760,6 +864,31 @@ static int hisi_ptt_register_pmu(struct hisi_ptt *hisi_ptt)
return perf_pmu_register(&hisi_ptt->hisi_ptt_pmu, pmu_name, -1);
}
+static void hisi_ptt_unregister_filter_update_notifier(void *data)
+{
+ struct hisi_ptt *hisi_ptt = data;
+
+ bus_unregister_notifier(&pci_bus_type, &hisi_ptt->hisi_ptt_nb);
+
+ /* Cancel any work that has been queued */
+ cancel_delayed_work_sync(&hisi_ptt->work);
+}
+
+/* Register the bus notifier for dynamically updating the filter list */
+static int hisi_ptt_register_filter_update_notifier(struct hisi_ptt *hisi_ptt)
+{
+ int ret;
+
+ hisi_ptt->hisi_ptt_nb.notifier_call = hisi_ptt_notifier_call;
+ ret = bus_register_notifier(&pci_bus_type, &hisi_ptt->hisi_ptt_nb);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(&hisi_ptt->pdev->dev,
+ hisi_ptt_unregister_filter_update_notifier,
+ hisi_ptt);
+}
+
/*
* The DMA of PTT trace can only use direct mapping, due to some
* hardware restriction. Check whether there is an IOMMU or the
@@ -832,6 +961,10 @@ static int hisi_ptt_probe(struct pci_dev *pdev,
return ret;
}
+ ret = hisi_ptt_register_filter_update_notifier(hisi_ptt);
+ if (ret)
+ pci_warn(pdev, "failed to register filter update notifier, ret = %d", ret);
+
ret = hisi_ptt_register_pmu(hisi_ptt);
if (ret) {
pci_err(pdev, "failed to register PMU device, ret = %d", ret);
@@ -10,11 +10,15 @@
#define _HISI_PTT_H
#include <linux/bits.h>
+#include <linux/kfifo.h>
#include <linux/list.h>
#include <linux/mutex.h>
+#include <linux/notifier.h>
#include <linux/pci.h>
#include <linux/perf_event.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
+#include <linux/workqueue.h>
#define DRV_NAME "hisi_ptt"
@@ -57,6 +61,12 @@
/* Wait time for hardware DMA to reset */
#define HISI_PTT_RESET_TIMEOUT_US 10UL
#define HISI_PTT_RESET_POLL_INTERVAL_US 1UL
+/* FIFO size for dynamically updating the PTT trace filter list. */
+#define HISI_PTT_FILTER_UPDATE_FIFO_SIZE 16
+/* Delay time for filter updating work */
+#define HISI_PTT_WORK_DELAY_MS 100UL
+/* Wait time for DMA hardware to reset */
+#define HISI_PTT_RESET_WAIT_MS 1000UL
/* Poll timeout and interval for waiting hardware work to finish */
#define HISI_PTT_WAIT_TRACE_TIMEOUT_US 100UL
#define HISI_PTT_WAIT_POLL_INTERVAL_US 10UL
@@ -112,6 +122,15 @@ struct hisi_ptt_filter_desc {
struct pci_dev *pdev;
};
+/**
+ * struct hisi_ptt_filter_update_info - information for PTT filter updating
+ * @pdev: the PCI device to update in the filter list
+ * @is_add: adding to the filter or not
+ */
+struct hisi_ptt_filter_update_info {
+ struct pci_dev *pdev;
+ bool is_add;
+};
/**
* struct hisi_ptt_pmu_buf - descriptor of the AUX buffer of PTT trace
@@ -139,9 +158,13 @@ struct hisi_ptt_pmu_buf {
* @port_filters: the filter list of root ports
* @req_filters: the filter list of requester ID
* @port_mask: port mask of the managed root ports
+ * @work: delayed work for filter updating
+ * @filter_update_lock: spinlock to protect the filter update fifo
+ * @filter_update_fifo: fifo of the filters waiting to update the filter list
*/
struct hisi_ptt {
struct hisi_ptt_trace_ctrl trace_ctrl;
+ struct notifier_block hisi_ptt_nb;
struct pmu hisi_ptt_pmu;
void __iomem *iobase;
struct pci_dev *pdev;
@@ -159,6 +182,17 @@ struct hisi_ptt {
struct list_head port_filters;
struct list_head req_filters;
u16 port_mask;
+
+ /*
+ * We use a delayed work here to avoid indefinitely waiting for
+ * the hisi_ptt->mutex which protecting the filter list. The
+ * work will be delayed only if the mutex can not be held,
+ * otherwise no delay will be applied.
+ */
+ struct delayed_work work;
+ spinlock_t filter_update_lock;
+ DECLARE_KFIFO(filter_update_kfifo, struct hisi_ptt_filter_update_info,
+ HISI_PTT_FILTER_UPDATE_FIFO_SIZE);
};
#define to_hisi_ptt(pmu) container_of(pmu, struct hisi_ptt, hisi_ptt_pmu)