|
@@ -598,88 +598,165 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-static void print_event_fields(struct trace_seq *s, struct list_head *head)
|
|
|
+enum {
|
|
|
+ FORMAT_HEADER = 1,
|
|
|
+ FORMAT_PRINTFMT = 2,
|
|
|
+};
|
|
|
+
|
|
|
+static void *f_next(struct seq_file *m, void *v, loff_t *pos)
|
|
|
{
|
|
|
+ struct ftrace_event_call *call = m->private;
|
|
|
struct ftrace_event_field *field;
|
|
|
+ struct list_head *head;
|
|
|
|
|
|
- list_for_each_entry_reverse(field, head, link) {
|
|
|
- /*
|
|
|
- * Smartly shows the array type(except dynamic array).
|
|
|
- * Normal:
|
|
|
- * field:TYPE VAR
|
|
|
- * If TYPE := TYPE[LEN], it is shown:
|
|
|
- * field:TYPE VAR[LEN]
|
|
|
- */
|
|
|
- const char *array_descriptor = strchr(field->type, '[');
|
|
|
+ (*pos)++;
|
|
|
|
|
|
- if (!strncmp(field->type, "__data_loc", 10))
|
|
|
- array_descriptor = NULL;
|
|
|
+ switch ((unsigned long)v) {
|
|
|
+ case FORMAT_HEADER:
|
|
|
+ head = &ftrace_common_fields;
|
|
|
|
|
|
- if (!array_descriptor) {
|
|
|
- trace_seq_printf(s, "\tfield:%s %s;\toffset:%u;"
|
|
|
- "\tsize:%u;\tsigned:%d;\n",
|
|
|
- field->type, field->name, field->offset,
|
|
|
- field->size, !!field->is_signed);
|
|
|
- } else {
|
|
|
- trace_seq_printf(s, "\tfield:%.*s %s%s;\toffset:%u;"
|
|
|
- "\tsize:%u;\tsigned:%d;\n",
|
|
|
- (int)(array_descriptor - field->type),
|
|
|
- field->type, field->name,
|
|
|
- array_descriptor, field->offset,
|
|
|
- field->size, !!field->is_signed);
|
|
|
- }
|
|
|
+ if (unlikely(list_empty(head)))
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ field = list_entry(head->prev, struct ftrace_event_field, link);
|
|
|
+ return field;
|
|
|
+
|
|
|
+ case FORMAT_PRINTFMT:
|
|
|
+ /* all done */
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ head = trace_get_fields(call);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * To separate common fields from event fields, the
|
|
|
+ * LSB is set on the first event field. Clear it in case.
|
|
|
+ */
|
|
|
+ v = (void *)((unsigned long)v & ~1L);
|
|
|
+
|
|
|
+ field = v;
|
|
|
+ /*
|
|
|
+ * If this is a common field, and at the end of the list, then
|
|
|
+ * continue with main list.
|
|
|
+ */
|
|
|
+ if (field->link.prev == &ftrace_common_fields) {
|
|
|
+ if (unlikely(list_empty(head)))
|
|
|
+ return NULL;
|
|
|
+ field = list_entry(head->prev, struct ftrace_event_field, link);
|
|
|
+ /* Set the LSB to notify f_show to print an extra newline */
|
|
|
+ field = (struct ftrace_event_field *)
|
|
|
+ ((unsigned long)field | 1);
|
|
|
+ return field;
|
|
|
}
|
|
|
+
|
|
|
+ /* If we are done tell f_show to print the format */
|
|
|
+ if (field->link.prev == head)
|
|
|
+ return (void *)FORMAT_PRINTFMT;
|
|
|
+
|
|
|
+ field = list_entry(field->link.prev, struct ftrace_event_field, link);
|
|
|
+
|
|
|
+ return field;
|
|
|
}
|
|
|
|
|
|
-static ssize_t
|
|
|
-event_format_read(struct file *filp, char __user *ubuf, size_t cnt,
|
|
|
- loff_t *ppos)
|
|
|
+static void *f_start(struct seq_file *m, loff_t *pos)
|
|
|
{
|
|
|
- struct ftrace_event_call *call = filp->private_data;
|
|
|
- struct list_head *head;
|
|
|
- struct trace_seq *s;
|
|
|
- char *buf;
|
|
|
- int r;
|
|
|
+ loff_t l = 0;
|
|
|
+ void *p;
|
|
|
|
|
|
- if (*ppos)
|
|
|
+ /* Start by showing the header */
|
|
|
+ if (!*pos)
|
|
|
+ return (void *)FORMAT_HEADER;
|
|
|
+
|
|
|
+ p = (void *)FORMAT_HEADER;
|
|
|
+ do {
|
|
|
+ p = f_next(m, p, &l);
|
|
|
+ } while (p && l < *pos);
|
|
|
+
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+static int f_show(struct seq_file *m, void *v)
|
|
|
+{
|
|
|
+ struct ftrace_event_call *call = m->private;
|
|
|
+ struct ftrace_event_field *field;
|
|
|
+ const char *array_descriptor;
|
|
|
+
|
|
|
+ switch ((unsigned long)v) {
|
|
|
+ case FORMAT_HEADER:
|
|
|
+ seq_printf(m, "name: %s\n", call->name);
|
|
|
+ seq_printf(m, "ID: %d\n", call->event.type);
|
|
|
+ seq_printf(m, "format:\n");
|
|
|
return 0;
|
|
|
|
|
|
- s = kmalloc(sizeof(*s), GFP_KERNEL);
|
|
|
- if (!s)
|
|
|
- return -ENOMEM;
|
|
|
+ case FORMAT_PRINTFMT:
|
|
|
+ seq_printf(m, "\nprint fmt: %s\n",
|
|
|
+ call->print_fmt);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
|
|
|
- trace_seq_init(s);
|
|
|
+ /*
|
|
|
+ * To separate common fields from event fields, the
|
|
|
+ * LSB is set on the first event field. Clear it and
|
|
|
+ * print a newline if it is set.
|
|
|
+ */
|
|
|
+ if ((unsigned long)v & 1) {
|
|
|
+ seq_putc(m, '\n');
|
|
|
+ v = (void *)((unsigned long)v & ~1L);
|
|
|
+ }
|
|
|
|
|
|
- trace_seq_printf(s, "name: %s\n", call->name);
|
|
|
- trace_seq_printf(s, "ID: %d\n", call->event.type);
|
|
|
- trace_seq_printf(s, "format:\n");
|
|
|
+ field = v;
|
|
|
|
|
|
- /* print common fields */
|
|
|
- print_event_fields(s, &ftrace_common_fields);
|
|
|
+ /*
|
|
|
+ * Smartly shows the array type(except dynamic array).
|
|
|
+ * Normal:
|
|
|
+ * field:TYPE VAR
|
|
|
+ * If TYPE := TYPE[LEN], it is shown:
|
|
|
+ * field:TYPE VAR[LEN]
|
|
|
+ */
|
|
|
+ array_descriptor = strchr(field->type, '[');
|
|
|
|
|
|
- trace_seq_putc(s, '\n');
|
|
|
+ if (!strncmp(field->type, "__data_loc", 10))
|
|
|
+ array_descriptor = NULL;
|
|
|
|
|
|
- /* print event specific fields */
|
|
|
- head = trace_get_fields(call);
|
|
|
- print_event_fields(s, head);
|
|
|
+ if (!array_descriptor)
|
|
|
+ seq_printf(m, "\tfield:%s %s;\toffset:%u;\tsize:%u;\tsigned:%d;\n",
|
|
|
+ field->type, field->name, field->offset,
|
|
|
+ field->size, !!field->is_signed);
|
|
|
+ else
|
|
|
+ seq_printf(m, "\tfield:%.*s %s%s;\toffset:%u;\tsize:%u;\tsigned:%d;\n",
|
|
|
+ (int)(array_descriptor - field->type),
|
|
|
+ field->type, field->name,
|
|
|
+ array_descriptor, field->offset,
|
|
|
+ field->size, !!field->is_signed);
|
|
|
|
|
|
- r = trace_seq_printf(s, "\nprint fmt: %s\n", call->print_fmt);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- if (!r) {
|
|
|
- /*
|
|
|
- * ug! The format output is bigger than a PAGE!!
|
|
|
- */
|
|
|
- buf = "FORMAT TOO BIG\n";
|
|
|
- r = simple_read_from_buffer(ubuf, cnt, ppos,
|
|
|
- buf, strlen(buf));
|
|
|
- goto out;
|
|
|
- }
|
|
|
+static void f_stop(struct seq_file *m, void *p)
|
|
|
+{
|
|
|
+}
|
|
|
|
|
|
- r = simple_read_from_buffer(ubuf, cnt, ppos,
|
|
|
- s->buffer, s->len);
|
|
|
- out:
|
|
|
- kfree(s);
|
|
|
- return r;
|
|
|
+static const struct seq_operations trace_format_seq_ops = {
|
|
|
+ .start = f_start,
|
|
|
+ .next = f_next,
|
|
|
+ .stop = f_stop,
|
|
|
+ .show = f_show,
|
|
|
+};
|
|
|
+
|
|
|
+static int trace_format_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ struct ftrace_event_call *call = inode->i_private;
|
|
|
+ struct seq_file *m;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = seq_open(file, &trace_format_seq_ops);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ m = file->private_data;
|
|
|
+ m->private = call;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
@@ -877,8 +954,10 @@ static const struct file_operations ftrace_enable_fops = {
|
|
|
};
|
|
|
|
|
|
static const struct file_operations ftrace_event_format_fops = {
|
|
|
- .open = tracing_open_generic,
|
|
|
- .read = event_format_read,
|
|
|
+ .open = trace_format_open,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = seq_release,
|
|
|
};
|
|
|
|
|
|
static const struct file_operations ftrace_event_id_fops = {
|