linux/kernel/trace/trace_mmiotrace.c
Linus Torvalds 081e5bf2a9 tracing updates for v7.3:
- Expose btf_ids to trace events
 
   In order to allow BPF programs to attach to system call trace events (which
   are actually pseudo trace events built on top of raw_syscall events),
   expose the BTF ID of the events. This will allow BPF programs better
   precision in attaching to events.
 
 - Use "u64" to assign to hist_field->type
 
   Instead of using kstrdup("u64", GFP_KERNEL) to assign the hist_field->type,
   just point it to "u64" instead. The hist_field->type is freed via
   kfree_const().
 
 - Replace kmalloc()/strcpy() with kstrdup() for trace_printk
 
   Instead of having two calls to copy the module format string, just use
   kstrdup().
 
 - Use __free() in trace event histograms and triggres where possible
 
 - Use seq_buf in trace event code instead of strcat()
 
   Instead of calculating the size of the buffer to use and filling it with
   strcat(), use the seq_buf infrastructure that takes care of making sure
   not to overflow the string size.
 
 - Reject invalid preemptirq_delay_test CPU affinity
 
   The preempt_delay_test module can take an invalid CPU affinity mask and
   create confusing output. Simply have the module reject invalid affinity
   masks.
 
 - Prevent division by zero in ftrace_ops sample module code
 
   If the ftrace_ops sample module code receives the module parameter
   nr_function_calls set to zero, it can cause a division by zero error.
 
 - Warn when an event dereferences a parameter in TP_printk()
 
   On boot up and module load, the trace event TP_printk() is scanned for
   possible bugs. As the TP_printk() code is executed when the user reads the
   "trace" file and processes the data written when the trace_event executed,
   the data it reads can be literally days old. The scan currently checks for
   dereferencing printk formats like "%pI6". But it does not check if the
   parameters themselves have a dereference like:
 
   TP_printk("offset %08x: value %08x",
            (u32)(__entry->addr - __entry->edma->membase), __entry->value)
 
   __entry represents the pointer to the event on the ring buffer. The
   __entry->edma->membase is dereferencing a pointer on the ring buffer to
   find membase, but the __entry->edma may no longer be a valid pointer.
 
   Warn on this case too.
 
 - Replace some strcpy() with strscpy()
 
 - Clean up mmiotrace events to use assign_type() macro
 
   The assign_type() macro makes sure the event type is indeed the type that
   is being parsed. The mmiotrace trace was written before that macro was
   created so it just simply typecasted the pointer.
 
   Replace the typecasting with the macro.
 
 - Have the ENUM processing to numbers only process what is added
 
   The code that converts ENUMs to their numbers in the trace events scanned
   all events to do the processing. This was true when a module was loaded
   too. That is, instead of processing just the events for the module, it
   processed *all* events. Even the builtin ones that were processed at boot
   up.
 
   Add a check for the event->module matching mod if it is a module before
   processing it.
 -----BEGIN PGP SIGNATURE-----
 
 iIoEABYKADIWIQRRSw7ePDh/lE+zeZMp5XQQmuv6qgUCan+WhRQccm9zdGVkdEBn
 b29kbWlzLm9yZwAKCRAp5XQQmuv6qk1sAQDIvuux39ii379T9hTm7hgXF9Dr8aH7
 RgxILUmr3c4y6QD+Iv9QQto+NfauqjkEMdPuVdOyUpDCYPTRw9i/YCty7Qk=
 =vya7
 -----END PGP SIGNATURE-----

Merge tag 'trace-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull tracing updates from Steven Rostedt:

 - Expose btf_ids to trace events

   In order to allow BPF programs to attach to system call trace events
   (which are actually pseudo trace events built on top of raw_syscall
   events), expose the BTF ID of the events. This will allow BPF
   programs better precision in attaching to events.

 - Use "u64" to assign to hist_field->type

   Instead of using kstrdup("u64", GFP_KERNEL) to assign the
   hist_field->type, just point it to "u64" instead. The
   hist_field->type is freed via kfree_const().

 - Replace kmalloc()/strcpy() with kstrdup() for trace_printk

   Instead of having two calls to copy the module format string, just
   use kstrdup().

 - Use __free() in trace event histograms and triggres where possible

 - Use seq_buf in trace event code instead of strcat()

   Instead of calculating the size of the buffer to use and filling it
   with strcat(), use the seq_buf infrastructure that takes care of
   making sure not to overflow the string size.

 - Reject invalid preemptirq_delay_test CPU affinity

   The preempt_delay_test module can take an invalid CPU affinity mask
   and create confusing output. Simply have the module reject invalid
   affinity masks.

 - Prevent division by zero in ftrace_ops sample module code

   If the ftrace_ops sample module code receives the module parameter
   nr_function_calls set to zero, it can cause a division by zero error.

 - Warn when an event dereferences a parameter in TP_printk()

   On boot up and module load, the trace event TP_printk() is scanned
   for possible bugs. As the TP_printk() code is executed when the user
   reads the "trace" file and processes the data written when the
   trace_event executed, the data it reads can be literally days old.
   The scan currently checks for dereferencing printk formats like
   "%pI6". But it does not check if the parameters themselves have a
   dereference like:

	TP_printk("offset %08x: value %08x",
		(u32)(__entry->addr - __entry->edma->membase), __entry->value)

   __entry represents the pointer to the event on the ring buffer. The
   __entry->edma->membase is dereferencing a pointer on the ring buffer
   to find membase, but the __entry->edma may no longer be a valid
   pointer.

   Warn on this case too.

 - Replace some strcpy() with strscpy()

 - Clean up mmiotrace events to use assign_type() macro

   The assign_type() macro makes sure the event type is indeed the type
   that is being parsed. The mmiotrace trace was written before that
   macro was created so it just simply typecasted the pointer.

   Replace the typecasting with the macro.

 - Have the ENUM processing to numbers only process what is added

   The code that converts ENUMs to their numbers in the trace events
   scanned all events to do the processing. This was true when a module
   was loaded too. That is, instead of processing just the events for
   the module, it processed *all* events. Even the builtin ones that
   were processed at boot up.

   Add a check for the event->module matching mod if it is a module
   before processing it.

* tag 'trace-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (21 commits)
  tracing: Have trace_event_update_all() only handle module that is loading
  tracing: Cleanup event_enable_trigger_parse() by using __free()
  tracing: Report every TP_printk double dereference
  tracing/mmiotrace: Use trace_assign_type() in mmio_print_mark()
  tracing: Make per-template BTF id lists file-local
  tracing: Use seq_buf for string concatenation
  tracing: Use strscpy() instead of strcpy() in trace_sched_switch
  tracing: Warn when an event dereferences a pointer in TP_printk()
  samples/ftrace: Prevent division by zero when nr_function_calls is zero
  tracing: Reject invalid preemptirq_delay_test CPU affinity
  fgraph: Use trace_seq_putc() in print_graph_return()
  tracing/user_events: Replace a seq_printf() call by seq_puts() in user_seq_show()
  tracing/user_events: Use seq_putc() in two functions
  tracing: Bound histogram expression strings with seq_buf
  tracing: Return ERR_PTR() from expr_str()
  tracing: Use __free() for expr_str() buffer
  kernel/trace/trace_printk: Use kstrdup() instead of kmalloc() and strcpy()
  tracing: Point constant hist field type to string literal
  selftests/bpf: Add test for tracepoint btf_ids tracefs file
  tracing: Expose tracepoint BTF ids via tracefs
  ...
2026-08-19 14:06:14 -07:00

363 lines
8.2 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Memory mapped I/O tracing
*
* Copyright (C) 2008 Pekka Paalanen <pq@iki.fi>
*/
#include <linux/kernel.h>
#include <linux/mmiotrace.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/atomic.h>
#include "trace.h"
#include "trace_output.h"
struct header_iter {
struct pci_dev *dev;
};
static struct trace_array *mmio_trace_array;
static bool overrun_detected;
static unsigned long prev_overruns;
static atomic_t dropped_count;
static void mmio_reset_data(struct trace_array *tr)
{
overrun_detected = false;
prev_overruns = 0;
atomic_set(&dropped_count, 0);
tracing_reset_online_cpus(&tr->array_buffer);
}
static int mmio_trace_init(struct trace_array *tr)
{
pr_debug("in %s\n", __func__);
mmio_trace_array = tr;
mmio_reset_data(tr);
enable_mmiotrace();
return 0;
}
static void mmio_trace_reset(struct trace_array *tr)
{
pr_debug("in %s\n", __func__);
disable_mmiotrace();
mmio_reset_data(tr);
mmio_trace_array = NULL;
}
static void mmio_trace_start(struct trace_array *tr)
{
pr_debug("in %s\n", __func__);
mmio_reset_data(tr);
}
static void mmio_print_pcidev(struct trace_seq *s, const struct pci_dev *dev)
{
int i;
resource_size_t start, end;
const struct pci_driver *drv = pci_dev_driver(dev);
trace_seq_printf(s, "PCIDEV %02x%02x %04x%04x %x",
dev->bus->number, dev->devfn,
dev->vendor, dev->device, dev->irq);
for (i = 0; i < 7; i++) {
start = dev->resource[i].start;
trace_seq_printf(s, " %llx",
(unsigned long long)(start |
(dev->resource[i].flags & PCI_REGION_FLAG_MASK)));
}
for (i = 0; i < 7; i++) {
start = dev->resource[i].start;
end = dev->resource[i].end;
trace_seq_printf(s, " %llx",
dev->resource[i].start < dev->resource[i].end ?
(unsigned long long)(end - start) + 1 : 0);
}
if (drv)
trace_seq_printf(s, " %s\n", drv->name);
else
trace_seq_puts(s, " \n");
}
static void destroy_header_iter(struct header_iter *hiter)
{
if (!hiter)
return;
pci_dev_put(hiter->dev);
kfree(hiter);
}
static void mmio_pipe_open(struct trace_iterator *iter)
{
struct header_iter *hiter;
struct trace_seq *s = &iter->seq;
trace_seq_puts(s, "VERSION 20070824\n");
hiter = kzalloc_obj(*hiter);
if (!hiter)
return;
hiter->dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
iter->private = hiter;
}
static void mmio_close(struct trace_iterator *iter)
{
struct header_iter *hiter = iter->private;
destroy_header_iter(hiter);
iter->private = NULL;
}
static unsigned long count_overruns(struct trace_iterator *iter)
{
unsigned long cnt = atomic_xchg(&dropped_count, 0);
unsigned long over = ring_buffer_overruns(iter->array_buffer->buffer);
if (over > prev_overruns)
cnt += over - prev_overruns;
prev_overruns = over;
return cnt;
}
static ssize_t mmio_read(struct trace_iterator *iter, struct file *filp,
char __user *ubuf, size_t cnt, loff_t *ppos)
{
ssize_t ret;
struct header_iter *hiter = iter->private;
struct trace_seq *s = &iter->seq;
unsigned long n;
n = count_overruns(iter);
if (n) {
/* XXX: This is later than where events were lost. */
trace_seq_printf(s, "MARK 0.000000 Lost %lu events.\n", n);
if (!overrun_detected)
pr_warn("mmiotrace has lost events\n");
overrun_detected = true;
goto print_out;
}
if (!hiter || !hiter->dev)
return 0;
mmio_print_pcidev(s, hiter->dev);
hiter->dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, hiter->dev);
if (!hiter->dev) {
destroy_header_iter(hiter);
iter->private = NULL;
}
print_out:
ret = trace_seq_to_user(s, ubuf, cnt);
return (ret == -EBUSY) ? 0 : ret;
}
static enum print_line_t mmio_print_rw(struct trace_iterator *iter)
{
struct trace_entry *entry = iter->ent;
struct trace_mmiotrace_rw *field;
struct mmiotrace_rw *rw;
struct trace_seq *s = &iter->seq;
unsigned long long t = ns2usecs(iter->ts);
unsigned long usec_rem = do_div(t, USEC_PER_SEC);
unsigned secs = (unsigned long)t;
trace_assign_type(field, entry);
rw = &field->rw;
switch (rw->opcode) {
case MMIO_READ:
trace_seq_printf(s,
"R %d %u.%06lu %d 0x%llx 0x%lx 0x%lx %d\n",
rw->width, secs, usec_rem, rw->map_id,
(unsigned long long)rw->phys,
rw->value, rw->pc, 0);
break;
case MMIO_WRITE:
trace_seq_printf(s,
"W %d %u.%06lu %d 0x%llx 0x%lx 0x%lx %d\n",
rw->width, secs, usec_rem, rw->map_id,
(unsigned long long)rw->phys,
rw->value, rw->pc, 0);
break;
case MMIO_UNKNOWN_OP:
trace_seq_printf(s,
"UNKNOWN %u.%06lu %d 0x%llx %02lx,%02lx,"
"%02lx 0x%lx %d\n",
secs, usec_rem, rw->map_id,
(unsigned long long)rw->phys,
(rw->value >> 16) & 0xff, (rw->value >> 8) & 0xff,
(rw->value >> 0) & 0xff, rw->pc, 0);
break;
default:
trace_seq_puts(s, "rw what?\n");
break;
}
return trace_handle_return(s);
}
static enum print_line_t mmio_print_map(struct trace_iterator *iter)
{
struct trace_entry *entry = iter->ent;
struct trace_mmiotrace_map *field;
struct mmiotrace_map *m;
struct trace_seq *s = &iter->seq;
unsigned long long t = ns2usecs(iter->ts);
unsigned long usec_rem = do_div(t, USEC_PER_SEC);
unsigned secs = (unsigned long)t;
trace_assign_type(field, entry);
m = &field->map;
switch (m->opcode) {
case MMIO_PROBE:
trace_seq_printf(s,
"MAP %u.%06lu %d 0x%llx 0x%lx 0x%lx 0x%lx %d\n",
secs, usec_rem, m->map_id,
(unsigned long long)m->phys, m->virt, m->len,
0UL, 0);
break;
case MMIO_UNPROBE:
trace_seq_printf(s,
"UNMAP %u.%06lu %d 0x%lx %d\n",
secs, usec_rem, m->map_id, 0UL, 0);
break;
default:
trace_seq_puts(s, "map what?\n");
break;
}
return trace_handle_return(s);
}
static enum print_line_t mmio_print_mark(struct trace_iterator *iter)
{
struct trace_entry *entry = iter->ent;
struct print_entry *print;
const char *msg;
struct trace_seq *s = &iter->seq;
unsigned long long t = ns2usecs(iter->ts);
unsigned long usec_rem = do_div(t, USEC_PER_SEC);
unsigned secs = (unsigned long)t;
trace_assign_type(print, entry);
msg = print->buf;
/* The trailing newline must be in the message. */
trace_seq_printf(s, "MARK %u.%06lu %s", secs, usec_rem, msg);
return trace_handle_return(s);
}
static enum print_line_t mmio_print_line(struct trace_iterator *iter)
{
switch (iter->ent->type) {
case TRACE_MMIO_RW:
return mmio_print_rw(iter);
case TRACE_MMIO_MAP:
return mmio_print_map(iter);
case TRACE_PRINT:
return mmio_print_mark(iter);
default:
return TRACE_TYPE_HANDLED; /* ignore unknown entries */
}
}
static struct tracer mmio_tracer __read_mostly =
{
.name = "mmiotrace",
.init = mmio_trace_init,
.reset = mmio_trace_reset,
.start = mmio_trace_start,
.pipe_open = mmio_pipe_open,
.close = mmio_close,
.pipe_close = mmio_close,
.read = mmio_read,
.print_line = mmio_print_line,
.noboot = true,
};
__init static int init_mmio_trace(void)
{
return register_tracer(&mmio_tracer);
}
device_initcall(init_mmio_trace);
static void __trace_mmiotrace_rw(struct trace_array *tr,
struct mmiotrace_rw *rw)
{
struct trace_buffer *buffer;
struct ring_buffer_event *event;
struct trace_mmiotrace_rw *entry;
unsigned int trace_ctx;
if (!tr)
return;
buffer = tr->array_buffer.buffer;
trace_ctx = tracing_gen_ctx_flags(0);
event = trace_buffer_lock_reserve(buffer, TRACE_MMIO_RW,
sizeof(*entry), trace_ctx);
if (!event) {
atomic_inc(&dropped_count);
return;
}
entry = ring_buffer_event_data(event);
entry->rw = *rw;
trace_buffer_unlock_commit(tr, buffer, event, trace_ctx);
}
void mmio_trace_rw(struct mmiotrace_rw *rw)
{
struct trace_array *tr = mmio_trace_array;
__trace_mmiotrace_rw(tr, rw);
}
static void __trace_mmiotrace_map(struct trace_array *tr,
struct mmiotrace_map *map)
{
struct trace_buffer *buffer;
struct ring_buffer_event *event;
struct trace_mmiotrace_map *entry;
unsigned int trace_ctx;
if (!tr)
return;
buffer = tr->array_buffer.buffer;
trace_ctx = tracing_gen_ctx_flags(0);
event = trace_buffer_lock_reserve(buffer, TRACE_MMIO_MAP,
sizeof(*entry), trace_ctx);
if (!event) {
atomic_inc(&dropped_count);
return;
}
entry = ring_buffer_event_data(event);
entry->map = *map;
trace_buffer_unlock_commit(tr, buffer, event, trace_ctx);
}
void mmio_trace_mapping(struct mmiotrace_map *map)
{
struct trace_array *tr = mmio_trace_array;
__trace_mmiotrace_map(tr, map);
}
int mmio_trace_printk(const char *fmt, va_list args)
{
return trace_vprintk(0, fmt, args);
}