diff options
Diffstat (limited to 'drivers/hwtracing/ptt/hisi_ptt.c')
-rw-r--r-- | drivers/hwtracing/ptt/hisi_ptt.c | 170 |
1 files changed, 162 insertions, 8 deletions
diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index 548cfef51ace..31471488fc91 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -357,24 +357,42 @@ static int hisi_ptt_register_irq(struct hisi_ptt *hisi_ptt) static void hisi_ptt_del_free_filter(struct hisi_ptt *hisi_ptt, struct hisi_ptt_filter_desc *filter) { + if (filter->is_port) + hisi_ptt->port_mask &= ~hisi_ptt_get_filter_val(filter->devid, true); + list_del(&filter->list); + kfree(filter->name); kfree(filter); } static struct hisi_ptt_filter_desc * -hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, struct pci_dev *pdev) +hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) { struct hisi_ptt_filter_desc *filter; + u8 devfn = devid & 0xff; + char *filter_name; + + filter_name = kasprintf(GFP_KERNEL, "%04x:%02x:%02x.%d", pci_domain_nr(hisi_ptt->pdev->bus), + PCI_BUS_NUM(devid), PCI_SLOT(devfn), PCI_FUNC(devfn)); + if (!filter_name) { + pci_err(hisi_ptt->pdev, "failed to allocate name for filter %04x:%02x:%02x.%d\n", + pci_domain_nr(hisi_ptt->pdev->bus), PCI_BUS_NUM(devid), + PCI_SLOT(devfn), PCI_FUNC(devfn)); + return NULL; + } filter = kzalloc(sizeof(*filter), GFP_KERNEL); if (!filter) { pci_err(hisi_ptt->pdev, "failed to add filter for %s\n", - pci_name(pdev)); + filter_name); + kfree(filter_name); return NULL; } - filter->devid = PCI_DEVID(pdev->bus->number, pdev->devfn); - filter->is_port = pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT; + filter->name = filter_name; + filter->is_port = is_port; + filter->devid = devid; + if (filter->is_port) { list_add_tail(&filter->list, &hisi_ptt->port_filters); @@ -387,6 +405,102 @@ hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, struct pci_dev *pdev) return filter; } +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; + + hisi_ptt = container_of(delayed_work, struct hisi_ptt, work); + + if (!mutex_trylock(&hisi_ptt->filter_lock)) { + schedule_delayed_work(&hisi_ptt->work, HISI_PTT_WORK_DELAY_MS); + return; + } + + while (kfifo_get(&hisi_ptt->filter_update_kfifo, &info)) { + if (info.is_add) { + /* + * Notify the users if failed to add this filter, others + * still work and available. See the comments in + * hisi_ptt_init_filters(). + */ + filter = hisi_ptt_alloc_add_filter(hisi_ptt, info.devid, info.is_port); + if (!filter) + continue; + } else { + struct hisi_ptt_filter_desc *tmp; + struct list_head *target_list; + + target_list = info.is_port ? &hisi_ptt->port_filters : + &hisi_ptt->req_filters; + + list_for_each_entry_safe(filter, tmp, target_list, list) + if (filter->devid == info.devid) { + hisi_ptt_del_free_filter(hisi_ptt, filter); + break; + } + } + } + + mutex_unlock(&hisi_ptt->filter_lock); +} + +/* + * 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 pci_dev *pdev, *root_port; + struct device *dev = data; + u32 port_devid; + + pdev = to_pci_dev(dev); + root_port = pcie_find_root_port(pdev); + if (!root_port) + return 0; + + port_devid = PCI_DEVID(root_port->bus->number, root_port->devfn); + if (port_devid < hisi_ptt->lower_bdf || + port_devid > hisi_ptt->upper_bdf) + return 0; + + info.is_port = pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT; + info.devid = PCI_DEVID(pdev->bus->number, pdev->devfn); + + 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; + } + + /* + * The FIFO size is 16 which is sufficient for almost all the cases, + * since each PCIe core will have most 8 Root Ports (typically only + * 1~4 Root Ports). On failure log the failed filter and let user + * handle it. + */ + 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(pdev)); + + return 0; +} + static int hisi_ptt_init_filters(struct pci_dev *pdev, void *data) { struct pci_dev *root_port = pcie_find_root_port(pdev); @@ -407,7 +521,8 @@ static int hisi_ptt_init_filters(struct pci_dev *pdev, void *data) * should be partial initialized and users would know which filter fails * through the log. Other functions of PTT device are still available. */ - filter = hisi_ptt_alloc_add_filter(hisi_ptt, pdev); + filter = hisi_ptt_alloc_add_filter(hisi_ptt, PCI_DEVID(pdev->bus->number, pdev->devfn), + pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT); if (!filter) return -ENOMEM; @@ -466,8 +581,13 @@ 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); + INIT_KFIFO(hisi_ptt->filter_update_kfifo); + spin_lock_init(&hisi_ptt->filter_update_lock); + INIT_LIST_HEAD(&hisi_ptt->port_filters); INIT_LIST_HEAD(&hisi_ptt->req_filters); + mutex_init(&hisi_ptt->filter_lock); ret = hisi_ptt_config_trace_buf(hisi_ptt); if (ret) @@ -620,6 +740,7 @@ static int hisi_ptt_trace_valid_filter(struct hisi_ptt *hisi_ptt, u64 config) { unsigned long val, port_mask = hisi_ptt->port_mask; struct hisi_ptt_filter_desc *filter; + int ret = 0; hisi_ptt->trace_ctrl.is_port = FIELD_GET(HISI_PTT_PMU_FILTER_IS_PORT, config); val = FIELD_GET(HISI_PTT_PMU_FILTER_VAL_MASK, config); @@ -633,16 +754,20 @@ static int hisi_ptt_trace_valid_filter(struct hisi_ptt *hisi_ptt, u64 config) * For Requester ID filters, walk the available filter list to see * whether we have one matched. */ + mutex_lock(&hisi_ptt->filter_lock); if (!hisi_ptt->trace_ctrl.is_port) { list_for_each_entry(filter, &hisi_ptt->req_filters, list) { if (val == hisi_ptt_get_filter_val(filter->devid, filter->is_port)) - return 0; + goto out; } } else if (bitmap_subset(&val, &port_mask, BITS_PER_LONG)) { - return 0; + goto out; } - return -EINVAL; + ret = -EINVAL; +out: + mutex_unlock(&hisi_ptt->filter_lock); + return ret; } static void hisi_ptt_pmu_init_configs(struct hisi_ptt *hisi_ptt, struct perf_event *event) @@ -916,6 +1041,31 @@ static int hisi_ptt_register_pmu(struct hisi_ptt *hisi_ptt) &hisi_ptt->hisi_ptt_pmu); } +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 mappings due to some * hardware restriction. Check whether there is no IOMMU or the @@ -987,6 +1137,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); |