]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - kernel/events/core.c
Merge remote-tracking branch 'tip/auto-latest'
[karo-tx-linux.git] / kernel / events / core.c
index 647eb2bc2ebb5d1a01a2bdd4d549451826be25f7..ad278b5403d0e98fd9f6dd2bc5b7914b7e63a3ae 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/perf_event.h>
 #include <linux/ftrace_event.h>
 #include <linux/hw_breakpoint.h>
+#include <linux/mm_types.h>
 
 #include "internal.h"
 
@@ -3764,6 +3765,132 @@ int perf_unregister_guest_info_callbacks(struct perf_guest_info_callbacks *cbs)
 }
 EXPORT_SYMBOL_GPL(perf_unregister_guest_info_callbacks);
 
+static void
+perf_output_sample_regs(struct perf_output_handle *handle,
+                       struct pt_regs *regs, u64 mask)
+{
+       int bit;
+
+       for_each_set_bit(bit, (const unsigned long *) &mask,
+                        sizeof(mask) * BITS_PER_BYTE) {
+               u64 val;
+
+               val = perf_reg_value(regs, bit);
+               perf_output_put(handle, val);
+       }
+}
+
+static void perf_sample_regs_user(struct perf_regs_user *regs_user,
+                                 struct pt_regs *regs)
+{
+       if (!user_mode(regs)) {
+               if (current->mm)
+                       regs = task_pt_regs(current);
+               else
+                       regs = NULL;
+       }
+
+       if (regs) {
+               regs_user->regs = regs;
+               regs_user->abi  = perf_reg_abi(current);
+       }
+}
+
+/*
+ * Get remaining task size from user stack pointer.
+ *
+ * It'd be better to take stack vma map and limit this more
+ * precisly, but there's no way to get it safely under interrupt,
+ * so using TASK_SIZE as limit.
+ */
+static u64 perf_ustack_task_size(struct pt_regs *regs)
+{
+       unsigned long addr = perf_user_stack_pointer(regs);
+
+       if (!addr || addr >= TASK_SIZE)
+               return 0;
+
+       return TASK_SIZE - addr;
+}
+
+static u16
+perf_sample_ustack_size(u16 stack_size, u16 header_size,
+                       struct pt_regs *regs)
+{
+       u64 task_size;
+
+       /* No regs, no stack pointer, no dump. */
+       if (!regs)
+               return 0;
+
+       /*
+        * Check if we fit in with the requested stack size into the:
+        * - TASK_SIZE
+        *   If we don't, we limit the size to the TASK_SIZE.
+        *
+        * - remaining sample size
+        *   If we don't, we customize the stack size to
+        *   fit in to the remaining sample size.
+        */
+
+       task_size  = min((u64) USHRT_MAX, perf_ustack_task_size(regs));
+       stack_size = min(stack_size, (u16) task_size);
+
+       /* Current header size plus static size and dynamic size. */
+       header_size += 2 * sizeof(u64);
+
+       /* Do we fit in with the current stack dump size? */
+       if ((u16) (header_size + stack_size) < header_size) {
+               /*
+                * If we overflow the maximum size for the sample,
+                * we customize the stack dump size to fit in.
+                */
+               stack_size = USHRT_MAX - header_size - sizeof(u64);
+               stack_size = round_up(stack_size, sizeof(u64));
+       }
+
+       return stack_size;
+}
+
+static void
+perf_output_sample_ustack(struct perf_output_handle *handle, u64 dump_size,
+                         struct pt_regs *regs)
+{
+       /* Case of a kernel thread, nothing to dump */
+       if (!regs) {
+               u64 size = 0;
+               perf_output_put(handle, size);
+       } else {
+               unsigned long sp;
+               unsigned int rem;
+               u64 dyn_size;
+
+               /*
+                * We dump:
+                * static size
+                *   - the size requested by user or the best one we can fit
+                *     in to the sample max size
+                * data
+                *   - user stack dump data
+                * dynamic size
+                *   - the actual dumped size
+                */
+
+               /* Static size. */
+               perf_output_put(handle, dump_size);
+
+               /* Data. */
+               sp = perf_user_stack_pointer(regs);
+               rem = __output_copy_user(handle, (void *) sp, dump_size);
+               dyn_size = dump_size - rem;
+
+               perf_output_skip(handle, rem);
+
+               /* Dynamic size. */
+               perf_output_put(handle, dyn_size);
+       }
+}
+
 static void __perf_event_header__init_id(struct perf_event_header *header,
                                         struct perf_sample_data *data,
                                         struct perf_event *event)
@@ -4024,6 +4151,28 @@ void perf_output_sample(struct perf_output_handle *handle,
                        perf_output_put(handle, nr);
                }
        }
+
+       if (sample_type & PERF_SAMPLE_REGS_USER) {
+               u64 abi = data->regs_user.abi;
+
+               /*
+                * If there are no regs to dump, notice it through
+                * first u64 being zero (PERF_SAMPLE_REGS_ABI_NONE).
+                */
+               perf_output_put(handle, abi);
+
+               if (abi) {
+                       u64 mask = event->attr.sample_regs_user;
+                       perf_output_sample_regs(handle,
+                                               data->regs_user.regs,
+                                               mask);
+               }
+       }
+
+       if (sample_type & PERF_SAMPLE_STACK_USER)
+               perf_output_sample_ustack(handle,
+                                         data->stack_user_size,
+                                         data->regs_user.regs);
 }
 
 void perf_prepare_sample(struct perf_event_header *header,
@@ -4075,6 +4224,49 @@ void perf_prepare_sample(struct perf_event_header *header,
                }
                header->size += size;
        }
+
+       if (sample_type & PERF_SAMPLE_REGS_USER) {
+               /* regs dump ABI info */
+               int size = sizeof(u64);
+
+               perf_sample_regs_user(&data->regs_user, regs);
+
+               if (data->regs_user.regs) {
+                       u64 mask = event->attr.sample_regs_user;
+                       size += hweight64(mask) * sizeof(u64);
+               }
+
+               header->size += size;
+       }
+
+       if (sample_type & PERF_SAMPLE_STACK_USER) {
+               /*
+                * Either we need PERF_SAMPLE_STACK_USER bit to be allways
+                * processed as the last one or have additional check added
+                * in case new sample type is added, because we could eat
+                * up the rest of the sample size.
+                */
+               struct perf_regs_user *uregs = &data->regs_user;
+               u16 stack_size = event->attr.sample_stack_user;
+               u16 size = sizeof(u64);
+
+               if (!uregs->abi)
+                       perf_sample_regs_user(uregs, regs);
+
+               stack_size = perf_sample_ustack_size(stack_size, header->size,
+                                                    uregs->regs);
+
+               /*
+                * If there is something to dump, add space for the dump
+                * itself and for the field that tells the dynamic size,
+                * which is how many have been actually dumped.
+                */
+               if (stack_size)
+                       size += sizeof(u64) + stack_size;
+
+               data->stack_user_size = stack_size;
+               header->size += size;
+       }
 }
 
 static void perf_event_output(struct perf_event *event,
@@ -6151,6 +6343,28 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr,
                        attr->branch_sample_type = mask;
                }
        }
+
+       if (attr->sample_type & PERF_SAMPLE_REGS_USER) {
+               ret = perf_reg_validate(attr->sample_regs_user);
+               if (ret)
+                       return ret;
+       }
+
+       if (attr->sample_type & PERF_SAMPLE_STACK_USER) {
+               if (!arch_perf_have_user_stack_dump())
+                       return -ENOSYS;
+
+               /*
+                * We have __u32 type for the size, but so far
+                * we can only use __u16 as maximum due to the
+                * __u16 sample size limit.
+                */
+               if (attr->sample_stack_user >= USHRT_MAX)
+                       ret = -EINVAL;
+               else if (!IS_ALIGNED(attr->sample_stack_user, sizeof(u64)))
+                       ret = -EINVAL;
+       }
+
 out:
        return ret;