/* * The value of the tuning event are composed of two parts: main event code * in BIT[0,15] and subevent code in BIT[16,23]. For example, qox_tx_cpl is * a subevent of 'Tx path QoS control' which for tuning the weight of Tx * completion TLPs. See hisi_ptt.rst documentation for more information.
*/ #define HISI_PTT_TUNE_QOS_TX_CPL (0x4 | (3 << 16)) #define HISI_PTT_TUNE_QOS_TX_NP (0x4 | (4 << 16)) #define HISI_PTT_TUNE_QOS_TX_P (0x4 | (5 << 16)) #define HISI_PTT_TUNE_RX_ALLOC_BUF_LEVEL (0x5 | (6 << 16)) #define HISI_PTT_TUNE_TX_ALLOC_BUF_LEVEL (0x5 | (7 << 16))
/* Check device idle before start trace */ if (!hisi_ptt_wait_trace_hw_idle(hisi_ptt)) {
pci_err(hisi_ptt->pdev, "Failed to start trace, the device is still busy\n"); return -EBUSY;
}
ctrl->started = true;
/* 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);
hisi_ptt_wait_dma_reset_done(hisi_ptt);
val = readl(hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
val &= ~HISI_PTT_TRACE_CTRL_RST;
writel(val, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
/* Reset the index of current buffer */
hisi_ptt->trace_ctrl.buf_index = 0;
/* Zero the trace buffers */ for (i = 0; i < HISI_PTT_TRACE_BUF_CNT; i++)
memset(ctrl->trace_buf[i].addr, 0, HISI_PTT_TRACE_BUF_SIZE);
/* Clear the interrupt status */
writel(HISI_PTT_TRACE_INT_STAT_MASK, hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT);
writel(0, hisi_ptt->iobase + HISI_PTT_TRACE_INT_MASK);
/* Set the trace control register */
val = FIELD_PREP(HISI_PTT_TRACE_CTRL_TYPE_SEL, ctrl->type);
val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_RXTX_SEL, ctrl->direction);
val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_DATA_FORMAT, ctrl->format);
val |= FIELD_PREP(HISI_PTT_TRACE_CTRL_TARGET_SEL, hisi_ptt->trace_ctrl.filter); if (!hisi_ptt->trace_ctrl.is_port)
val |= HISI_PTT_TRACE_CTRL_FILTER_MODE;
/* Start the Trace */
val |= HISI_PTT_TRACE_CTRL_EN;
writel(val, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL);
buf = perf_get_aux(handle); if (!buf || !handle->size) return -EINVAL;
addr = ctrl->trace_buf[ctrl->buf_index].addr;
/* * If we're going to stop, read the size of already traced data from * HISI_PTT_TRACE_WR_STS. Otherwise we're coming from the interrupt, * the data size is always HISI_PTT_TRACE_BUF_SIZE.
*/ if (stop) {
u32 reg;
/* * Always commit the data to the AUX buffer in time to make sure * userspace got enough time to consume the data. * * If we're not going to stop, apply a new one and check whether * there's enough room for the next trace.
*/
perf_aux_output_end(handle, size); if (!stop) {
buf = perf_aux_output_begin(handle, event); if (!buf) return -EINVAL;
status = readl(hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT); if (!(status & HISI_PTT_TRACE_INT_STAT_MASK)) return IRQ_NONE;
buf_idx = ffs(status) - 1;
/* Clear the interrupt status of buffer @buf_idx */
writel(status, hisi_ptt->iobase + HISI_PTT_TRACE_INT_STAT);
/* * Update the AUX buffer and cache the current buffer index, * as we need to know this and save the data when the trace * is ended out of the interrupt handler. End the trace * if the updating fails.
*/ if (hisi_ptt_update_aux(hisi_ptt, buf_idx, false))
hisi_ptt_trace_end(hisi_ptt); else
hisi_ptt->trace_ctrl.buf_index = (buf_idx + 1) % HISI_PTT_TRACE_BUF_CNT;
staticint hisi_ptt_init_filter_attributes(struct hisi_ptt *hisi_ptt)
{ struct hisi_ptt_filter_desc *filter; int ret;
mutex_lock(&hisi_ptt->filter_lock);
/* * Register the reset callback in the first stage. In reset we traverse * the filters list to remove the sysfs attributes so the callback can * be called safely even without below filter attributes creation.
*/
ret = devm_add_action(&hisi_ptt->pdev->dev,
hisi_ptt_remove_all_filter_attributes,
hisi_ptt); if (ret) goto out;
list_for_each_entry(filter, &hisi_ptt->port_filters, list) {
ret = hisi_ptt_create_filter_attr(hisi_ptt, filter); if (ret) goto out;
}
list_for_each_entry(filter, &hisi_ptt->req_filters, list) {
ret = hisi_ptt_create_filter_attr(hisi_ptt, filter); if (ret) goto out;
}
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;
/* * If filters' sysfs entries hasn't been initialized, * then we're still at probe stage. Add the filters to * the list and later hisi_ptt_init_filter_attributes() * will create sysfs attributes for all the filters.
*/ if (hisi_ptt->sysfs_inited &&
hisi_ptt_create_filter_attr(hisi_ptt, filter)) {
hisi_ptt_del_free_filter(hisi_ptt, filter); continue;
}
} else { struct hisi_ptt_filter_desc *tmp; struct list_head *target_list;
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));
/* * We won't fail the probe if filter allocation failed here. The filters * 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, pci_dev_id(pdev),
pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT); if (!filter) return -ENOMEM;
ctrl->trace_buf = devm_kcalloc(dev, HISI_PTT_TRACE_BUF_CNT, sizeof(*ctrl->trace_buf), GFP_KERNEL); if (!ctrl->trace_buf) return -ENOMEM;
for (i = 0; i < HISI_PTT_TRACE_BUF_CNT; ++i) {
ctrl->trace_buf[i].addr = dmam_alloc_coherent(dev, HISI_PTT_TRACE_BUF_SIZE,
&ctrl->trace_buf[i].dma,
GFP_KERNEL); if (!ctrl->trace_buf[i].addr) return -ENOMEM;
}
/* Configure the trace DMA buffer */ for (i = 0; i < HISI_PTT_TRACE_BUF_CNT; i++) {
writel(lower_32_bits(ctrl->trace_buf[i].dma),
hisi_ptt->iobase + HISI_PTT_TRACE_ADDR_BASE_LO_0 +
i * HISI_PTT_TRACE_ADDR_STRIDE);
writel(upper_32_bits(ctrl->trace_buf[i].dma),
hisi_ptt->iobase + HISI_PTT_TRACE_ADDR_BASE_HI_0 +
i * HISI_PTT_TRACE_ADDR_STRIDE);
}
writel(HISI_PTT_TRACE_BUF_SIZE, hisi_ptt->iobase + HISI_PTT_TRACE_ADDR_SIZE);
ret = hisi_ptt_config_trace_buf(hisi_ptt); if (ret) return ret;
/* * The device range register provides the information about the root * ports which the RCiEP can control and trace. The RCiEP and the root * ports which it supports are on the same PCIe core, with same domain * number but maybe different bus number. The device range register * will tell us which root ports we can support, Bit[31:16] indicates * the upper BDF numbers of the root port, while Bit[15:0] indicates * the lower.
*/
reg = readl(hisi_ptt->iobase + HISI_PTT_DEVICE_RANGE);
hisi_ptt->upper_bdf = FIELD_GET(HISI_PTT_DEVICE_RANGE_UPPER, reg);
hisi_ptt->lower_bdf = FIELD_GET(HISI_PTT_DEVICE_RANGE_LOWER, reg);
bus = pci_find_bus(pci_domain_nr(pdev->bus), PCI_BUS_NUM(hisi_ptt->upper_bdf)); if (bus)
pci_walk_bus(bus, hisi_ptt_init_filters, hisi_ptt);
ret = devm_add_action_or_reset(&pdev->dev, hisi_ptt_release_filters, hisi_ptt); if (ret) return ret;
/* * Bit 19 indicates the filter type, 1 for Root Port filter and 0 for Requester * filter. Bit[15:0] indicates the filter value, for Root Port filter it's * a bit mask of desired ports and for Requester filter it's the Requester ID * of the desired PCIe function. Bit[18:16] is reserved for extension. * * See hisi_ptt.rst documentation for detailed information.
*/
PMU_FORMAT_ATTR(filter, "config:0-19");
PMU_FORMAT_ATTR(direction, "config:20-23");
PMU_FORMAT_ATTR(type, "config:24-31");
PMU_FORMAT_ATTR(format, "config:32-35");
staticint hisi_ptt_trace_valid_direction(u32 val)
{ /* * The direction values have different effects according to the data * format (specified in the parentheses). TLP set A/B means different * set of TLP types. See hisi_ptt.rst documentation for more details.
*/ staticconst u32 hisi_ptt_trace_available_direction[] = {
0, /* inbound(4DW) or reserved(8DW) */
1, /* outbound(4DW) */
2, /* {in, out}bound(4DW) or inbound(8DW), TLP set A */
3, /* {in, out}bound(4DW) or inbound(8DW), TLP set B */
}; int i;
for (i = 0; i < ARRAY_SIZE(hisi_ptt_trace_available_direction); i++) { if (val == hisi_ptt_trace_available_direction[i]) return 0;
}
return -EINVAL;
}
staticint hisi_ptt_trace_valid_type(u32 val)
{ /* Different types can be set simultaneously */ staticconst u32 hisi_ptt_trace_available_type[] = {
1, /* posted_request */
2, /* non-posted_request */
4, /* completion */
}; int i;
if (!val) return -EINVAL;
/* * Walk the available list and clear the valid bits of * the config. If there is any resident bit after the * walk then the config is invalid.
*/ for (i = 0; i < ARRAY_SIZE(hisi_ptt_trace_available_type); i++)
val &= ~hisi_ptt_trace_available_type[i];
for (i = 0; i < ARRAY_SIZE(hisi_ptt_trace_availble_format); i++) { if (val == hisi_ptt_trace_availble_format[i]) return 0;
}
return -EINVAL;
}
staticint hisi_ptt_trace_valid_filter(struct hisi_ptt *hisi_ptt, u64 config)
{ unsignedlong 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);
/* * Port filters are defined as bit mask. For port filters, check * the bits in the @val are within the range of hisi_ptt->port_mask * and whether it's empty or not, otherwise user has specified * some unsupported root ports. * * 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)) goto out;
}
} elseif (bitmap_subset(&val, &port_mask, BITS_PER_LONG)) { goto out;
}
ret = -EINVAL;
out:
mutex_unlock(&hisi_ptt->filter_lock); return ret;
}
staticvoid hisi_ptt_pmu_start(struct perf_event *event, int flags)
{ struct hisi_ptt *hisi_ptt = to_hisi_ptt(event->pmu); struct perf_output_handle *handle = &hisi_ptt->trace_ctrl.handle; struct hw_perf_event *hwc = &event->hw; struct device *dev = event->pmu->dev; struct hisi_ptt_pmu_buf *buf; int cpu = event->cpu; int ret;
hwc->state = 0;
/* Serialize the perf process if user specified several CPUs */
spin_lock(&hisi_ptt->pmu_lock); if (hisi_ptt->trace_ctrl.started) {
dev_dbg(dev, "trace has already started\n"); goto stop;
}
/* * Handle the interrupt on the same cpu which starts the trace to avoid * context mismatch. Otherwise we'll trigger the WARN from the perf * core in event_function_local(). If CPU passed is offline we'll fail * here, just log it since we can do nothing here.
*/
ret = irq_set_affinity(hisi_ptt->trace_irq, cpumask_of(cpu)); if (ret)
dev_warn(dev, "failed to set the affinity of trace interrupt\n");
hisi_ptt->trace_ctrl.on_cpu = cpu;
buf = perf_aux_output_begin(handle, event); if (!buf) {
dev_dbg(dev, "aux output begin failed\n"); goto stop;
}
buf->pos = handle->head % buf->length;
hisi_ptt_pmu_init_configs(hisi_ptt, event);
ret = hisi_ptt_trace_start(hisi_ptt); if (ret) {
dev_dbg(dev, "trace start failed, ret = %d\n", ret);
perf_aux_output_end(handle, 0); goto stop;
}
staticint hisi_ptt_pmu_add(struct perf_event *event, int flags)
{ struct hisi_ptt *hisi_ptt = to_hisi_ptt(event->pmu); struct hw_perf_event *hwc = &event->hw; int cpu = event->cpu;
/* Only allow the cpus on the device's node to add the event */ if (!cpumask_test_cpu(cpu, cpumask_of_node(dev_to_node(&hisi_ptt->pdev->dev)))) return 0;
/* Cancel any work that has been queued */
cancel_delayed_work_sync(&hisi_ptt->work);
}
/* Register the bus notifier for dynamically updating the filter list */ staticint 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;
/* * The DMA of PTT trace can only use direct mappings due to some * hardware restriction. Check whether there is no IOMMU or the * policy of the IOMMU domain is passthrough, otherwise the trace * cannot work. * * The PTT device is supposed to behind an ARM SMMUv3, which * should have passthrough the device by a quirk.
*/ staticint hisi_ptt_check_iommu_mapping(struct pci_dev *pdev)
{ struct iommu_domain *iommu_domain;
if (!hisi_ptt->trace_ctrl.started || src != cpu) return 0;
target = cpumask_any_but(cpumask_of_node(dev_to_node(&hisi_ptt->pdev->dev)), cpu); if (target >= nr_cpu_ids) {
dev_err(dev, "no available cpu for perf context migration\n"); return 0;
}
/* * Also make sure the interrupt bind to the migrated CPU as well. Warn * the user on failure here.
*/ if (irq_set_affinity(hisi_ptt->trace_irq, cpumask_of(target)))
dev_warn(dev, "failed to set the affinity of trace interrupt\n");
hisi_ptt->trace_ctrl.on_cpu = target; return 0;
}
staticint __init hisi_ptt_init(void)
{ int ret;
ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, DRV_NAME, NULL,
hisi_ptt_cpu_teardown); if (ret < 0) return ret;
hisi_ptt_pmu_online = ret;
ret = pci_register_driver(&hisi_ptt_driver); if (ret)
cpuhp_remove_multi_state(hisi_ptt_pmu_online);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.